From 83c69f4cf381ce8218a861b4349bc2f207397aa2 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Wed, 24 Nov 2010 18:14:36 +0900 Subject: [PATCH] Implementing GtkComboBox using GtkTreeMenu ! First iteration at implementing combo box using a delegate treemenu, almost everything is working. Still need to finalize sensitivity issues in GtkTreeMenu (and should go ahead and pass through GtkComboBox code with a fine comb...). --- gtk/gtkcellview.c | 11 +- gtk/gtkcombobox.c | 6448 ++++++++++++++++++--------------------------- gtk/gtktreemenu.c | 96 +- 3 files changed, 2620 insertions(+), 3935 deletions(-) diff --git a/gtk/gtkcellview.c b/gtk/gtkcellview.c index c31ce7b3af..7cb39e9965 100644 --- a/gtk/gtkcellview.c +++ b/gtk/gtkcellview.c @@ -829,9 +829,14 @@ row_changed_cb (GtkTreeModel *model, row_path = gtk_tree_row_reference_get_path (view->priv->displayed_row); - /* Resize everything in our context if our row changed */ - if (gtk_tree_path_compare (row_path, path) == 0) - gtk_cell_area_context_flush (view->priv->context); + if (row_path) + { + /* Resize everything in our context if our row changed */ + if (gtk_tree_path_compare (row_path, path) == 0) + gtk_cell_area_context_flush (view->priv->context); + + gtk_tree_path_free (row_path); + } } } diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index 753212f804..dfc39607eb 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -39,6 +39,8 @@ #include "gtktreeselection.h" #include "gtkvseparator.h" #include "gtkwindow.h" +#include "gtkcellareabox.h" +#include "gtktreemenu.h" #include "gtkprivate.h" #include @@ -51,7 +53,6 @@ #include "gtkmarshalers.h" #include "gtkintl.h" -#include "gtkentryprivate.h" #include "gtktreeprivate.h" @@ -83,94 +84,6 @@ /* WELCOME, to THE house of evil code */ - -typedef struct _ComboCellInfo ComboCellInfo; -struct _ComboCellInfo -{ - GtkCellRenderer *cell; - GSList *attributes; - - GtkCellLayoutDataFunc func; - gpointer func_data; - GDestroyNotify destroy; - - guint expand : 1; - guint pack : 1; -}; - - -struct _GtkComboBoxPrivate -{ - GtkTreeModel *model; - - gint col_column; - gint row_column; - - gint wrap_width; - GtkShadowType shadow_type; - - gint active; /* Only temporary */ - GtkTreeRowReference *active_row; - - GtkWidget *tree_view; - GtkTreeViewColumn *column; - - GtkWidget *cell_view; - GtkWidget *cell_view_frame; - - GtkWidget *button; - GtkWidget *box; - GtkWidget *arrow; - GtkWidget *separator; - - GtkWidget *popup_widget; - GtkWidget *popup_window; - GtkWidget *scrolled_window; - - gulong inserted_id; - gulong deleted_id; - gulong reordered_id; - gulong changed_id; - guint popup_idle_id; - guint activate_button; - guint32 activate_time; - guint scroll_timer; - guint resize_idle_id; - - gint minimum_width; - gint natural_width; - - /* For "has-entry" specific behavior we track - * an automated cell renderer and text column */ - gint text_column; - GtkCellRenderer *text_renderer; - - gint id_column; - - GSList *cells; - - guint popup_in_progress : 1; - guint popup_shown : 1; - guint add_tearoffs : 1; - guint has_frame : 1; - guint is_cell_renderer : 1; - guint editing_canceled : 1; - guint auto_scroll : 1; - guint focus_on_click : 1; - guint button_sensitivity : 2; - guint has_entry : 1; - guint popup_fixed_width : 1; - - GtkTreeViewRowSeparatorFunc row_separator_func; - gpointer row_separator_data; - GDestroyNotify row_separator_destroy; - - GdkDevice *grab_pointer; - GdkDevice *grab_keyboard; - - gchar *tearoff_title; -}; - /* While debugging this evil code, I have learned that * there are actually 4 modes to this widget, which can * be characterized as follows @@ -225,51 +138,12 @@ struct _GtkComboBoxPrivate * */ -enum { - CHANGED, - MOVE_ACTIVE, - POPUP, - POPDOWN, - LAST_SIGNAL -}; - -enum { - PROP_0, - PROP_MODEL, - PROP_WRAP_WIDTH, - PROP_ROW_SPAN_COLUMN, - PROP_COLUMN_SPAN_COLUMN, - PROP_ACTIVE, - PROP_ADD_TEAROFFS, - PROP_TEAROFF_TITLE, - PROP_HAS_FRAME, - PROP_FOCUS_ON_CLICK, - PROP_POPUP_SHOWN, - PROP_BUTTON_SENSITIVITY, - PROP_EDITING_CANCELED, - PROP_HAS_ENTRY, - PROP_ENTRY_TEXT_COLUMN, - PROP_POPUP_FIXED_WIDTH, - PROP_ID_COLUMN, - PROP_ACTIVE_ID -}; - -static guint combo_box_signals[LAST_SIGNAL] = {0,}; - -#define BONUS_PADDING 4 -#define SCROLL_TIME 100 - -/* common */ - -static void gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface); -static void gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface); +/* GObjectClass */ static GObject *gtk_combo_box_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties); static void gtk_combo_box_dispose (GObject *object); static void gtk_combo_box_finalize (GObject *object); -static void gtk_combo_box_destroy (GtkWidget *widget); - static void gtk_combo_box_set_property (GObject *object, guint prop_id, const GValue *value, @@ -279,22 +153,58 @@ static void gtk_combo_box_get_property (GObject *object, GValue *value, GParamSpec *spec); -static void gtk_combo_box_state_changed (GtkWidget *widget, - GtkStateType previous); -static void gtk_combo_box_grab_focus (GtkWidget *widget); -static void gtk_combo_box_style_updated (GtkWidget *widget); -static void gtk_combo_box_button_toggled (GtkWidget *widget, - gpointer data); -static void gtk_combo_box_button_state_flags_changed (GtkWidget *widget, - GtkStateFlags previous, - gpointer data); +/* GtkWidgetClass */ +static void gtk_combo_box_destroy (GtkWidget *widget); +static void gtk_combo_box_state_changed (GtkWidget *widget, + GtkStateType previous); +static void gtk_combo_box_grab_focus (GtkWidget *widget); +static void gtk_combo_box_style_updated (GtkWidget *widget); +static void gtk_combo_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gtk_combo_box_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_combo_box_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_combo_box_get_preferred_width_for_height (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size); +static void gtk_combo_box_get_preferred_height_for_width (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size); +static gboolean gtk_combo_box_draw (GtkWidget *widget, + cairo_t *cr); +static gboolean gtk_combo_box_scroll_event (GtkWidget *widget, + GdkEventScroll *event); + +/* GtkContainerClass */ static void gtk_combo_box_add (GtkContainer *container, GtkWidget *widget); static void gtk_combo_box_remove (GtkContainer *container, GtkWidget *widget); +static void gtk_combo_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); + +/* GtkComboBoxClass (binding handlers) */ +static void gtk_combo_box_real_move_active (GtkComboBox *combo_box, + GtkScrollType scroll); +static void gtk_combo_box_real_popup (GtkComboBox *combo_box); +static gboolean gtk_combo_box_real_popdown (GtkComboBox *combo_box); + -static ComboCellInfo *gtk_combo_box_get_cell_info (GtkComboBox *combo_box, - GtkCellRenderer *cell); +/* GtkTreeModel signals */ +static void gtk_combo_box_model_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data); +static void gtk_combo_box_model_row_deleted (GtkTreeModel *model, + GtkTreePath *path, + gpointer user_data); static void gtk_combo_box_menu_show (GtkWidget *menu, gpointer user_data); @@ -303,64 +213,19 @@ static void gtk_combo_box_menu_hide (GtkWidget *menu, static void gtk_combo_box_set_popup_widget (GtkComboBox *combo_box, GtkWidget *popup); -static void gtk_combo_box_menu_position_below (GtkMenu *menu, - gint *x, - gint *y, - gint *push_in, - gpointer user_data); -static void gtk_combo_box_menu_position_over (GtkMenu *menu, - gint *x, - gint *y, - gint *push_in, - gpointer user_data); -static void gtk_combo_box_menu_position (GtkMenu *menu, - gint *x, - gint *y, - gint *push_in, - gpointer user_data); - -static void gtk_combo_box_update_requested_width(GtkComboBox *combo_box, - GtkTreePath *path); -static void gtk_combo_box_remeasure (GtkComboBox *combo_box); static void gtk_combo_box_unset_model (GtkComboBox *combo_box); +static void gtk_combo_box_button_toggled (GtkWidget *widget, + gpointer data); +static void gtk_combo_box_button_state_changed (GtkWidget *widget, + GtkStateType previous, + gpointer data); -static void gtk_combo_box_size_allocate (GtkWidget *widget, - GtkAllocation *allocation); -static void gtk_combo_box_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data); -static gboolean gtk_combo_box_draw (GtkWidget *widget, - cairo_t *cr); -static gboolean gtk_combo_box_scroll_event (GtkWidget *widget, - GdkEventScroll *event); static void gtk_combo_box_set_active_internal (GtkComboBox *combo_box, GtkTreePath *path); static void gtk_combo_box_check_appearance (GtkComboBox *combo_box); -static void gtk_combo_box_real_move_active (GtkComboBox *combo_box, - GtkScrollType scroll); -static void gtk_combo_box_real_popup (GtkComboBox *combo_box); -static gboolean gtk_combo_box_real_popdown (GtkComboBox *combo_box); -/* listening to the model */ -static void gtk_combo_box_model_row_inserted (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data); -static void gtk_combo_box_model_row_deleted (GtkTreeModel *model, - GtkTreePath *path, - gpointer user_data); -static void gtk_combo_box_model_rows_reordered (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - gpointer user_data); -static void gtk_combo_box_model_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data); static void gtk_combo_box_model_row_expanded (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, @@ -398,94 +263,45 @@ static gboolean gtk_combo_box_list_select_func (GtkTreeSelection *selection, gboolean path_currently_selected, gpointer data); -static void gtk_combo_box_list_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data); static void gtk_combo_box_list_popup_resize (GtkComboBox *combo_box); /* menu */ static void gtk_combo_box_menu_setup (GtkComboBox *combo_box, gboolean add_children); -static void gtk_combo_box_menu_fill (GtkComboBox *combo_box); -static void gtk_combo_box_menu_fill_level (GtkComboBox *combo_box, - GtkWidget *menu, - GtkTreeIter *iter); -static void gtk_combo_box_update_title (GtkComboBox *combo_box); static void gtk_combo_box_menu_destroy (GtkComboBox *combo_box); - -static void gtk_combo_box_relayout_item (GtkComboBox *combo_box, - GtkWidget *item, - GtkTreeIter *iter, - GtkWidget *last); -static void gtk_combo_box_relayout (GtkComboBox *combo_box); - +static void gtk_combo_box_update_title (GtkComboBox *combo_box); static gboolean gtk_combo_box_menu_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data); -static void gtk_combo_box_menu_item_activate (GtkWidget *item, - gpointer user_data); - -static void gtk_combo_box_update_sensitivity (GtkComboBox *combo_box); -static void gtk_combo_box_menu_row_inserted (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data); -static void gtk_combo_box_menu_row_deleted (GtkTreeModel *model, - GtkTreePath *path, - gpointer user_data); -static void gtk_combo_box_menu_rows_reordered (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - gpointer user_data); -static void gtk_combo_box_menu_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data); +static void gtk_combo_box_menu_activate (GtkWidget *menu, + const gchar *path, + GtkComboBox *combo_box); static gboolean gtk_combo_box_menu_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data); static void gtk_combo_box_menu_popup (GtkComboBox *combo_box, guint button, guint32 activate_time); -static GtkWidget *gtk_cell_view_menu_item_new (GtkComboBox *combo_box, - GtkTreeModel *model, - GtkTreeIter *iter); - -/* cell layout */ -static void gtk_combo_box_cell_layout_pack_start (GtkCellLayout *layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_combo_box_cell_layout_pack_end (GtkCellLayout *layout, - GtkCellRenderer *cell, - gboolean expand); -static GList *gtk_combo_box_cell_layout_get_cells (GtkCellLayout *layout); -static void gtk_combo_box_cell_layout_clear (GtkCellLayout *layout); -static void gtk_combo_box_cell_layout_add_attribute (GtkCellLayout *layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column); -static void gtk_combo_box_cell_layout_set_cell_data_func (GtkCellLayout *layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy); -static void gtk_combo_box_cell_layout_clear_attributes (GtkCellLayout *layout, - GtkCellRenderer *cell); -static void gtk_combo_box_cell_layout_reorder (GtkCellLayout *layout, - GtkCellRenderer *cell, - gint position); + +static void gtk_combo_box_menu_position_below (GtkMenu *menu, + gint *x, + gint *y, + gint *push_in, + gpointer user_data); +static void gtk_combo_box_menu_position_over (GtkMenu *menu, + gint *x, + gint *y, + gint *push_in, + gpointer user_data); +static void gtk_combo_box_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gint *push_in, + gpointer user_data); + static gboolean gtk_combo_box_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); -static void gtk_combo_box_sync_cells (GtkComboBox *combo_box, - GtkCellLayout *cell_layout); -static void combo_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data); static void gtk_combo_box_child_show (GtkWidget *widget, GtkComboBox *combo_box); static void gtk_combo_box_child_hide (GtkWidget *widget, @@ -496,11 +312,7 @@ static void gtk_combo_box_entry_contents_changed (GtkEntry *e gpointer user_data); static void gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, gpointer user_data); - - -/* GtkBuildable method implementation */ -static GtkBuildableIface *parent_buildable_iface; - +/* GtkBuildableIface */ static void gtk_combo_box_buildable_init (GtkBuildableIface *iface); static gboolean gtk_combo_box_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, @@ -517,74 +329,174 @@ static GObject *gtk_combo_box_buildable_get_internal_child (GtkBuildable *buil GtkBuilder *builder, const gchar *childname); - -/* GtkCellEditable method implementations */ +/* GtkCellEditable */ +static void gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface); static void gtk_combo_box_start_editing (GtkCellEditable *cell_editable, GdkEvent *event); -static void gtk_combo_box_get_preferred_width (GtkWidget *widget, - gint *minimum_size, - gint *natural_size); -static void gtk_combo_box_get_preferred_height (GtkWidget *widget, - gint *minimum_size, - gint *natural_size); -static void gtk_combo_box_get_preferred_width_for_height (GtkWidget *widget, - gint avail_size, - gint *minimum_size, - gint *natural_size); -static void gtk_combo_box_get_preferred_height_for_width (GtkWidget *widget, - gint avail_size, - gint *minimum_size, - gint *natural_size); +/* GtkCellLayoutIface */ +static void gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface); +static GtkCellArea *gtk_combo_box_cell_layout_get_area (GtkCellLayout *layout); -G_DEFINE_TYPE_WITH_CODE (GtkComboBox, gtk_combo_box, GTK_TYPE_BIN, - G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, - gtk_combo_box_cell_layout_init) - G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, - gtk_combo_box_cell_editable_init) - G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, - gtk_combo_box_buildable_init)) +struct _GtkComboBoxPrivate +{ + GtkTreeModel *model; + /* The cell area shared with the treeview or treemenu */ + GtkCellArea *area; -/* common */ -static void -gtk_combo_box_class_init (GtkComboBoxClass *klass) -{ - GObjectClass *object_class; - GtkContainerClass *container_class; - GtkWidgetClass *widget_class; - GtkBindingSet *binding_set; + gint col_column; + gint row_column; + gint id_column; - container_class = (GtkContainerClass *)klass; - container_class->forall = gtk_combo_box_forall; - container_class->add = gtk_combo_box_add; - container_class->remove = gtk_combo_box_remove; + gint wrap_width; + GtkShadowType shadow_type; - widget_class = (GtkWidgetClass *)klass; - widget_class->size_allocate = gtk_combo_box_size_allocate; - widget_class->draw = gtk_combo_box_draw; - widget_class->scroll_event = gtk_combo_box_scroll_event; - widget_class->mnemonic_activate = gtk_combo_box_mnemonic_activate; - widget_class->grab_focus = gtk_combo_box_grab_focus; - widget_class->style_updated = gtk_combo_box_style_updated; - widget_class->state_changed = gtk_combo_box_state_changed; - widget_class->get_preferred_width = gtk_combo_box_get_preferred_width; - widget_class->get_preferred_height = gtk_combo_box_get_preferred_height; - widget_class->get_preferred_height_for_width = gtk_combo_box_get_preferred_height_for_width; - widget_class->get_preferred_width_for_height = gtk_combo_box_get_preferred_width_for_height; - widget_class->destroy = gtk_combo_box_destroy; + gint active; /* Only temporary */ + GtkTreeRowReference *active_row; - object_class = (GObjectClass *)klass; - object_class->constructor = gtk_combo_box_constructor; - object_class->dispose = gtk_combo_box_dispose; - object_class->finalize = gtk_combo_box_finalize; - object_class->set_property = gtk_combo_box_set_property; - object_class->get_property = gtk_combo_box_get_property; - /* signals */ - /** - * GtkComboBox::changed: + /* The cellview displayed on the button */ + GtkWidget *cell_view; + GtkWidget *cell_view_frame; + + /* The treeview & column for list mode */ + GtkWidget *tree_view; + GtkTreeViewColumn *column; + + GtkWidget *button; + GtkWidget *box; + GtkWidget *arrow; + GtkWidget *separator; + + GtkWidget *popup_widget; + GtkWidget *popup_window; + GtkWidget *scrolled_window; + + gulong inserted_id; + gulong deleted_id; + guint popup_idle_id; + guint activate_button; + guint32 activate_time; + guint scroll_timer; + guint resize_idle_id; + + /* For "has-entry" specific behavior we track + * an automated cell renderer and text column */ + gint text_column; + GtkCellRenderer *text_renderer; + + guint popup_in_progress : 1; + guint popup_shown : 1; + guint add_tearoffs : 1; + guint has_frame : 1; + guint is_cell_renderer : 1; + guint editing_canceled : 1; + guint auto_scroll : 1; + guint focus_on_click : 1; + guint button_sensitivity : 2; + guint has_entry : 1; + guint popup_fixed_width : 1; + + GtkTreeViewRowSeparatorFunc row_separator_func; + gpointer row_separator_data; + GDestroyNotify row_separator_destroy; + + GdkDevice *grab_pointer; + GdkDevice *grab_keyboard; + + gchar *tearoff_title; +}; + + +enum { + CHANGED, + MOVE_ACTIVE, + POPUP, + POPDOWN, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_WRAP_WIDTH, + PROP_ROW_SPAN_COLUMN, + PROP_COLUMN_SPAN_COLUMN, + PROP_ACTIVE, + PROP_ADD_TEAROFFS, + PROP_TEAROFF_TITLE, + PROP_HAS_FRAME, + PROP_FOCUS_ON_CLICK, + PROP_POPUP_SHOWN, + PROP_BUTTON_SENSITIVITY, + PROP_EDITING_CANCELED, + PROP_HAS_ENTRY, + PROP_ENTRY_TEXT_COLUMN, + PROP_POPUP_FIXED_WIDTH, + PROP_ID_COLUMN, + PROP_ACTIVE_ID, + PROP_CELL_AREA +}; + +#define BONUS_PADDING 4 +#define SCROLL_TIME 100 + +static guint combo_box_signals[LAST_SIGNAL] = {0,}; + + +/* GtkBuildable method implementation */ +static GtkBuildableIface *parent_buildable_iface; + + +G_DEFINE_TYPE_WITH_CODE (GtkComboBox, gtk_combo_box, GTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_combo_box_cell_layout_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, + gtk_combo_box_cell_editable_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_combo_box_buildable_init)) + + +/* common */ +static void +gtk_combo_box_class_init (GtkComboBoxClass *klass) +{ + GObjectClass *object_class; + GtkContainerClass *container_class; + GtkWidgetClass *widget_class; + GtkBindingSet *binding_set; + + object_class = (GObjectClass *)klass; + object_class->constructor = gtk_combo_box_constructor; + object_class->dispose = gtk_combo_box_dispose; + object_class->finalize = gtk_combo_box_finalize; + object_class->set_property = gtk_combo_box_set_property; + object_class->get_property = gtk_combo_box_get_property; + + widget_class = (GtkWidgetClass *)klass; + widget_class->draw = gtk_combo_box_draw; + widget_class->scroll_event = gtk_combo_box_scroll_event; + widget_class->mnemonic_activate = gtk_combo_box_mnemonic_activate; + widget_class->grab_focus = gtk_combo_box_grab_focus; + widget_class->style_updated = gtk_combo_box_style_updated; + widget_class->state_changed = gtk_combo_box_state_changed; + widget_class->size_allocate = gtk_combo_box_size_allocate; + widget_class->get_preferred_width = gtk_combo_box_get_preferred_width; + widget_class->get_preferred_height = gtk_combo_box_get_preferred_height; + widget_class->get_preferred_height_for_width = gtk_combo_box_get_preferred_height_for_width; + widget_class->get_preferred_width_for_height = gtk_combo_box_get_preferred_width_for_height; + widget_class->destroy = gtk_combo_box_destroy; + + container_class = (GtkContainerClass *)klass; + container_class->forall = gtk_combo_box_forall; + container_class->add = gtk_combo_box_add; + container_class->remove = gtk_combo_box_remove; + + /* signals */ + /** + * GtkComboBox::changed: * @widget: the object which received the signal * * The changed signal is emitted when the active @@ -984,6 +896,20 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass) P_("The value of the id column " "for the active row"), NULL, GTK_PARAM_READWRITE)); + /** + * GtkComboBox:cell-area: + * + * The #GtkCellArea used to layout cell renderers for this combo box. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * GtkComboBox:popup-fixed-width: @@ -1047,35 +973,6 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass) g_type_class_add_private (object_class, sizeof (GtkComboBoxPrivate)); } -static void -gtk_combo_box_buildable_init (GtkBuildableIface *iface) -{ - parent_buildable_iface = g_type_interface_peek_parent (iface); - iface->add_child = _gtk_cell_layout_buildable_add_child; - iface->custom_tag_start = gtk_combo_box_buildable_custom_tag_start; - iface->custom_tag_end = gtk_combo_box_buildable_custom_tag_end; - iface->get_internal_child = gtk_combo_box_buildable_get_internal_child; -} - -static void -gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface) -{ - iface->pack_start = gtk_combo_box_cell_layout_pack_start; - iface->pack_end = gtk_combo_box_cell_layout_pack_end; - iface->get_cells = gtk_combo_box_cell_layout_get_cells; - iface->clear = gtk_combo_box_cell_layout_clear; - iface->add_attribute = gtk_combo_box_cell_layout_add_attribute; - iface->set_cell_data_func = gtk_combo_box_cell_layout_set_cell_data_func; - iface->clear_attributes = gtk_combo_box_cell_layout_clear_attributes; - iface->reorder = gtk_combo_box_cell_layout_reorder; -} - -static void -gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface) -{ - iface->start_editing = gtk_combo_box_start_editing; -} - static void gtk_combo_box_init (GtkComboBox *combo_box) { @@ -1087,14 +984,6 @@ gtk_combo_box_init (GtkComboBox *combo_box) GtkComboBoxPrivate); priv = combo_box->priv; - priv->cell_view = gtk_cell_view_new (); - gtk_widget_set_parent (priv->cell_view, GTK_WIDGET (combo_box)); - _gtk_bin_set_child (GTK_BIN (combo_box), priv->cell_view); - gtk_widget_show (priv->cell_view); - - priv->minimum_width = 0; - priv->natural_width = 0; - priv->wrap_width = 0; priv->active = -1; @@ -1117,12 +1006,105 @@ gtk_combo_box_init (GtkComboBox *combo_box) priv->text_renderer = NULL; priv->id_column = -1; - gtk_combo_box_check_appearance (combo_box); - context = gtk_widget_get_style_context (GTK_WIDGET (combo_box)); gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON); } +/****************************************************** + * GObjectClass * + ******************************************************/ +static GObject * +gtk_combo_box_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + GtkComboBox *combo_box; + GtkComboBoxPrivate *priv; + + object = G_OBJECT_CLASS (gtk_combo_box_parent_class)->constructor + (type, n_construct_properties, construct_properties); + + combo_box = GTK_COMBO_BOX (object); + priv = combo_box->priv; + + if (!priv->area) + { + GtkCellArea *area = gtk_cell_area_box_new (); + + priv->area = g_object_ref_sink (area); + } + + if (priv->has_entry) + { + GtkWidget *entry; + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_container_add (GTK_CONTAINER (combo_box), entry); + + priv->text_renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), + priv->text_renderer, TRUE); + + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), -1); + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gtk_combo_box_entry_active_changed), NULL); + } + else + { + priv->cell_view = gtk_cell_view_new_with_context (priv->area, NULL); + gtk_cell_view_set_fit_model (GTK_CELL_VIEW (priv->cell_view), TRUE); + gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), priv->model); + gtk_container_add (GTK_CONTAINER (combo_box), priv->cell_view); + gtk_widget_show (priv->cell_view); + } + + gtk_combo_box_check_appearance (combo_box); + + return object; +} + +static void +gtk_combo_box_dispose(GObject* object) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (object); + + if (GTK_IS_MENU (combo_box->priv->popup_widget)) + { + gtk_combo_box_menu_destroy (combo_box); + gtk_menu_detach (GTK_MENU (combo_box->priv->popup_widget)); + combo_box->priv->popup_widget = NULL; + } + + if (combo_box->priv->area) + { + g_object_unref (combo_box->priv->area); + combo_box->priv->area = NULL; + } + + G_OBJECT_CLASS (gtk_combo_box_parent_class)->dispose (object); +} + +static void +gtk_combo_box_finalize (GObject *object) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (object); + + if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view)) + gtk_combo_box_list_destroy (combo_box); + + if (combo_box->priv->popup_window) + gtk_widget_destroy (combo_box->priv->popup_window); + + gtk_combo_box_unset_model (combo_box); + + g_free (combo_box->priv->tearoff_title); + + G_OBJECT_CLASS (gtk_combo_box_parent_class)->finalize (object); +} + static void gtk_combo_box_set_property (GObject *object, guint prop_id, @@ -1130,6 +1112,7 @@ gtk_combo_box_set_property (GObject *object, GParamSpec *pspec) { GtkComboBox *combo_box = GTK_COMBO_BOX (object); + GtkCellArea *area; switch (prop_id) { @@ -1218,6 +1201,14 @@ gtk_combo_box_set_property (GObject *object, gtk_combo_box_set_active_id (combo_box, g_value_get_string (value)); break; + case PROP_CELL_AREA: + /* Construct-only, can only be assigned once */ + area = g_value_get_object (value); + + if (area) + combo_box->priv->area = g_object_ref_sink (area); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1303,1246 +1294,882 @@ gtk_combo_box_get_property (GObject *object, g_value_set_string (value, gtk_combo_box_get_active_id (combo_box)); break; + case PROP_CELL_AREA: + g_value_set_object (value, priv->area); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } -static void -gtk_combo_box_state_changed (GtkWidget *widget, - GtkStateType previous) +/****************************************************** + * GtkWidgetClass * + ******************************************************/ +static gboolean +gtk_combo_box_draw (GtkWidget *widget, + cairo_t *cr) { GtkComboBox *combo_box = GTK_COMBO_BOX (widget); GtkComboBoxPrivate *priv = combo_box->priv; - if (gtk_widget_get_realized (widget)) + if (priv->shadow_type != GTK_SHADOW_NONE) { - if (priv->tree_view && priv->cell_view) - { - GtkStyleContext *context; - GtkStateFlags state; - GdkRGBA *color; - - context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); + GtkStyleContext *context; + GtkStateFlags state; - gtk_style_context_get (context, state, - "background-color", &color, - NULL); + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + gtk_style_context_set_state (context, state); - gtk_cell_view_set_background_rgba (GTK_CELL_VIEW (priv->cell_view), color); - gdk_rgba_free (color); - } + gtk_render_background (context, cr, 0, 0, + gtk_widget_get_allocated_width (widget), + gtk_widget_get_allocated_height (widget)); + gtk_render_frame (context, cr, 0, 0, + gtk_widget_get_allocated_width (widget), + gtk_widget_get_allocated_height (widget)); } - gtk_widget_queue_draw (widget); -} - -static void -gtk_combo_box_button_state_flags_changed (GtkWidget *widget, - GtkStateFlags previous, - gpointer data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (data); - GtkComboBoxPrivate *priv = combo_box->priv; + gtk_container_propagate_draw (GTK_CONTAINER (widget), + priv->button, cr); - if (gtk_widget_get_realized (widget)) + if (priv->tree_view && priv->cell_view_frame) { - if (!priv->tree_view && priv->cell_view) - gtk_widget_set_state_flags (priv->cell_view, - gtk_widget_get_state_flags (widget), - TRUE); + gtk_container_propagate_draw (GTK_CONTAINER (widget), + priv->cell_view_frame, cr); } - gtk_widget_queue_draw (widget); -} - -static void -gtk_combo_box_check_appearance (GtkComboBox *combo_box) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - gboolean appears_as_list; - - /* if wrap_width > 0, then we are in grid-mode and forced to use - * unix style - */ - if (priv->wrap_width) - appears_as_list = FALSE; - else - gtk_widget_style_get (GTK_WIDGET (combo_box), - "appears-as-list", &appears_as_list, - NULL); + gtk_container_propagate_draw (GTK_CONTAINER (widget), + gtk_bin_get_child (GTK_BIN (widget)), + cr); - if (appears_as_list) - { - /* Destroy all the menu mode widgets, if they exist. */ - if (GTK_IS_MENU (priv->popup_widget)) - gtk_combo_box_menu_destroy (combo_box); + return FALSE; +} - /* Create the list mode widgets, if they don't already exist. */ - if (!GTK_IS_TREE_VIEW (priv->tree_view)) - gtk_combo_box_list_setup (combo_box); - } - else - { - /* Destroy all the list mode widgets, if they exist. */ - if (GTK_IS_TREE_VIEW (priv->tree_view)) - gtk_combo_box_list_destroy (combo_box); - /* Create the menu mode widgets, if they don't already exist. */ - if (!GTK_IS_MENU (priv->popup_widget)) - gtk_combo_box_menu_setup (combo_box, TRUE); - } +typedef struct { + GtkComboBox *combo; + GtkTreePath *path; + GtkTreeIter iter; + gboolean found; + gboolean set; + gboolean visible; +} SearchData; - gtk_widget_style_get (GTK_WIDGET (combo_box), - "shadow-type", &priv->shadow_type, - NULL); +static gboolean +path_visible (GtkTreeView *view, + GtkTreePath *path) +{ + GtkRBTree *tree; + GtkRBNode *node; + + /* Note that we rely on the fact that collapsed rows don't have nodes + */ + return _gtk_tree_view_find_node (view, path, &tree, &node); } -static void -gtk_combo_box_style_updated (GtkWidget *widget) +static gboolean +tree_column_row_is_sensitive (GtkComboBox *combo_box, + GtkTreeIter *iter) { - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *child; + GList *cells, *list; + gboolean sensitive; - gtk_combo_box_check_appearance (combo_box); + if (!priv->column) + return TRUE; - if (priv->tree_view && priv->cell_view) + if (priv->row_separator_func) { - GtkStyleContext *context; - GdkRGBA *color; - - context = gtk_widget_get_style_context (widget); - gtk_style_context_get (context, 0, - "background-color", &color, - NULL); - - gtk_cell_view_set_background_rgba (GTK_CELL_VIEW (priv->cell_view), - color); - - gdk_rgba_free (color); + if (priv->row_separator_func (priv->model, iter, + priv->row_separator_data)) + return FALSE; } - child = gtk_bin_get_child (GTK_BIN (combo_box)); - if (GTK_IS_ENTRY (child)) - g_object_set (child, "shadow-type", - GTK_SHADOW_NONE == priv->shadow_type ? - GTK_SHADOW_IN : GTK_SHADOW_NONE, NULL); -} + gtk_tree_view_column_cell_set_cell_data (priv->column, + priv->model, + iter, FALSE, FALSE); -static void -gtk_combo_box_button_toggled (GtkWidget *widget, - gpointer data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (data); + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->column)); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + sensitive = FALSE; + for (list = cells; list; list = list->next) { - if (!combo_box->priv->popup_in_progress) - gtk_combo_box_popup (combo_box); + g_object_get (list->data, "sensitive", &sensitive, NULL); + + if (sensitive) + break; } - else - gtk_combo_box_popdown (combo_box); + g_list_free (cells); + + return sensitive; } -static void -gtk_combo_box_add (GtkContainer *container, - GtkWidget *widget) +static gboolean +tree_next_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { - gboolean cell_view_removed = FALSE; - GtkComboBox *combo_box = GTK_COMBO_BOX (container); - GtkComboBoxPrivate *priv = combo_box->priv; - - if (priv->has_entry && !GTK_IS_ENTRY (widget)) - { - g_warning ("Attempting to add a widget with type %s to a GtkComboBox that needs an entry " - "(need an instance of GtkEntry or of a subclass)", - G_OBJECT_TYPE_NAME (widget)); - return; - } - - if (priv->cell_view != NULL && widget != priv->cell_view) - cell_view_removed = TRUE; + SearchData *search_data = (SearchData *)data; - if (priv->cell_view && - gtk_widget_get_parent (priv->cell_view)) + if (search_data->found) { - gtk_widget_unparent (priv->cell_view); - _gtk_bin_set_child (GTK_BIN (container), NULL); + if (!tree_column_row_is_sensitive (search_data->combo, iter)) + return FALSE; + + if (search_data->visible && + !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) + return FALSE; - /* since the cell_view was unparented, it's gone now */ - priv->cell_view = NULL; + search_data->set = TRUE; + search_data->iter = *iter; - gtk_widget_queue_resize (GTK_WIDGET (container)); + return TRUE; } + + if (gtk_tree_path_compare (path, search_data->path) == 0) + search_data->found = TRUE; - gtk_widget_set_parent (widget, GTK_WIDGET (container)); - _gtk_bin_set_child (GTK_BIN (container), widget); + return FALSE; +} - if (cell_view_removed) - { - if (!priv->tree_view && priv->separator) - { - gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (priv->separator)), - priv->separator); - priv->separator = NULL; +static gboolean +tree_next (GtkComboBox *combo, + GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *next, + gboolean visible) +{ + SearchData search_data; - gtk_widget_queue_resize (GTK_WIDGET (container)); - } - else if (priv->cell_view_frame) - { - gtk_widget_unparent (priv->cell_view_frame); - priv->cell_view_frame = NULL; - priv->box = NULL; - } - } + search_data.combo = combo; + search_data.path = gtk_tree_model_get_path (model, iter); + search_data.visible = visible; + search_data.found = FALSE; + search_data.set = FALSE; - if (priv->has_entry) - { - /* this flag is a hack to tell the entry to fill its allocation. - */ - _gtk_entry_set_is_cell_renderer (GTK_ENTRY (widget), TRUE); + gtk_tree_model_foreach (model, tree_next_func, &search_data); + + *next = search_data.iter; - g_signal_connect (widget, "changed", - G_CALLBACK (gtk_combo_box_entry_contents_changed), - combo_box); + gtk_tree_path_free (search_data.path); - gtk_entry_set_has_frame (GTK_ENTRY (widget), priv->has_frame); - } + return search_data.set; } -static void -gtk_combo_box_remove (GtkContainer *container, - GtkWidget *widget) +static gboolean +tree_prev_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { - GtkComboBox *combo_box = GTK_COMBO_BOX (container); - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreePath *path; - gboolean appears_as_list; + SearchData *search_data = (SearchData *)data; - if (priv->has_entry) + if (gtk_tree_path_compare (path, search_data->path) == 0) { - GtkWidget *child_widget; - - child_widget = gtk_bin_get_child (GTK_BIN (container)); - if (widget && widget == child_widget) - { - g_signal_handlers_disconnect_by_func (widget, - gtk_combo_box_entry_contents_changed, - container); - _gtk_entry_set_is_cell_renderer (GTK_ENTRY (widget), FALSE); - } + search_data->found = TRUE; + return TRUE; } + + if (!tree_column_row_is_sensitive (search_data->combo, iter)) + return FALSE; + + if (search_data->visible && + !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) + return FALSE; + + search_data->set = TRUE; + search_data->iter = *iter; + + return FALSE; +} - if (widget == priv->cell_view) - priv->cell_view = NULL; +static gboolean +tree_prev (GtkComboBox *combo, + GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *prev, + gboolean visible) +{ + SearchData search_data; - gtk_widget_unparent (widget); - _gtk_bin_set_child (GTK_BIN (container), NULL); + search_data.combo = combo; + search_data.path = gtk_tree_model_get_path (model, iter); + search_data.visible = visible; + search_data.found = FALSE; + search_data.set = FALSE; - if (gtk_widget_in_destruction (GTK_WIDGET (combo_box))) - return; + gtk_tree_model_foreach (model, tree_prev_func, &search_data); + + *prev = search_data.iter; - gtk_widget_queue_resize (GTK_WIDGET (container)); + gtk_tree_path_free (search_data.path); - if (!priv->tree_view) - appears_as_list = FALSE; - else - appears_as_list = TRUE; - - if (appears_as_list) - gtk_combo_box_list_destroy (combo_box); - else if (GTK_IS_MENU (priv->popup_widget)) - { - gtk_combo_box_menu_destroy (combo_box); - gtk_menu_detach (GTK_MENU (priv->popup_widget)); - priv->popup_widget = NULL; - } + return search_data.set; +} - if (!priv->cell_view) - { - priv->cell_view = gtk_cell_view_new (); - gtk_widget_set_parent (priv->cell_view, GTK_WIDGET (container)); - _gtk_bin_set_child (GTK_BIN (container), priv->cell_view); +static gboolean +tree_last_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + SearchData *search_data = (SearchData *)data; + + if (!tree_column_row_is_sensitive (search_data->combo, iter)) + return FALSE; - gtk_widget_show (priv->cell_view); - gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), - priv->model); - gtk_combo_box_sync_cells (combo_box, GTK_CELL_LAYOUT (priv->cell_view)); - } + /* Note that we rely on the fact that collapsed rows don't have nodes + */ + if (search_data->visible && + !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) + return FALSE; + + search_data->set = TRUE; + search_data->iter = *iter; + + return FALSE; +} +static gboolean +tree_last (GtkComboBox *combo, + GtkTreeModel *model, + GtkTreeIter *last, + gboolean visible) +{ + SearchData search_data; - if (appears_as_list) - gtk_combo_box_list_setup (combo_box); - else - gtk_combo_box_menu_setup (combo_box, TRUE); + search_data.combo = combo; + search_data.visible = visible; + search_data.set = FALSE; - if (gtk_tree_row_reference_valid (priv->active_row)) - { - path = gtk_tree_row_reference_get_path (priv->active_row); - gtk_combo_box_set_active_internal (combo_box, path); - gtk_tree_path_free (path); - } - else - gtk_combo_box_set_active_internal (combo_box, NULL); + gtk_tree_model_foreach (model, tree_last_func, &search_data); + + *last = search_data.iter; + + return search_data.set; } -static ComboCellInfo * -gtk_combo_box_get_cell_info (GtkComboBox *combo_box, - GtkCellRenderer *cell) + +static gboolean +tree_first_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { - GSList *i; + SearchData *search_data = (SearchData *)data; - for (i = combo_box->priv->cells; i; i = i->next) - { - ComboCellInfo *info = (ComboCellInfo *)i->data; + if (!tree_column_row_is_sensitive (search_data->combo, iter)) + return FALSE; + + if (search_data->visible && + !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) + return FALSE; + + search_data->set = TRUE; + search_data->iter = *iter; + + return TRUE; +} - if (info && info->cell == cell) - return info; - } +static gboolean +tree_first (GtkComboBox *combo, + GtkTreeModel *model, + GtkTreeIter *first, + gboolean visible) +{ + SearchData search_data; + + search_data.combo = combo; + search_data.visible = visible; + search_data.set = FALSE; - return NULL; + gtk_tree_model_foreach (model, tree_first_func, &search_data); + + *first = search_data.iter; + + return search_data.set; } -static void -gtk_combo_box_menu_show (GtkWidget *menu, - gpointer user_data) +static gboolean +gtk_combo_box_scroll_event (GtkWidget *widget, + GdkEventScroll *event) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkComboBoxPrivate *priv = combo_box->priv; + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + gboolean found; + GtkTreeIter iter; + GtkTreeIter new_iter; - gtk_combo_box_child_show (menu, user_data); + if (!gtk_combo_box_get_active_iter (combo_box, &iter)) + return TRUE; + + if (event->direction == GDK_SCROLL_UP) + found = tree_prev (combo_box, combo_box->priv->model, + &iter, &new_iter, FALSE); + else + found = tree_next (combo_box, combo_box->priv->model, + &iter, &new_iter, FALSE); + + if (found) + gtk_combo_box_set_active_iter (combo_box, &new_iter); - priv->popup_in_progress = TRUE; - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), - TRUE); - priv->popup_in_progress = FALSE; + return TRUE; } -static void -gtk_combo_box_menu_hide (GtkWidget *menu, - gpointer user_data) + +static gboolean +gtk_combo_box_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - gtk_combo_box_child_hide (menu,user_data); + if (combo_box->priv->has_entry) + { + GtkWidget* child; - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button), - FALSE); + child = gtk_bin_get_child (GTK_BIN (combo_box)); + if (child) + gtk_widget_grab_focus (child); + } + else + gtk_widget_grab_focus (combo_box->priv->button); + + return TRUE; } static void -gtk_combo_box_detacher (GtkWidget *widget, - GtkMenu *menu) +gtk_combo_box_grab_focus (GtkWidget *widget) { GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - GtkComboBoxPrivate *priv = combo_box->priv; - g_return_if_fail (priv->popup_widget == (GtkWidget *) menu); + if (combo_box->priv->has_entry) + { + GtkWidget *child; - g_signal_handlers_disconnect_by_func (menu->priv->toplevel, - gtk_combo_box_menu_show, - combo_box); - g_signal_handlers_disconnect_by_func (menu->priv->toplevel, - gtk_combo_box_menu_hide, - combo_box); - - priv->popup_widget = NULL; + child = gtk_bin_get_child (GTK_BIN (combo_box)); + if (child) + gtk_widget_grab_focus (child); + } + else + gtk_widget_grab_focus (combo_box->priv->button); } static void -gtk_combo_box_set_popup_widget (GtkComboBox *combo_box, - GtkWidget *popup) +gtk_combo_box_state_changed (GtkWidget *widget, + GtkStateType previous) { + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); GtkComboBoxPrivate *priv = combo_box->priv; - if (GTK_IS_MENU (priv->popup_widget)) - { - gtk_menu_detach (GTK_MENU (priv->popup_widget)); - priv->popup_widget = NULL; - } - else if (priv->popup_widget) + if (gtk_widget_get_realized (widget)) { - gtk_container_remove (GTK_CONTAINER (priv->scrolled_window), - priv->popup_widget); - g_object_unref (priv->popup_widget); - priv->popup_widget = NULL; - } + if (priv->tree_view && priv->cell_view) + { + GtkStyleContext *context; + GtkStateFlags state; + GdkRGBA *color; - if (GTK_IS_MENU (popup)) - { - GtkMenu *menu = GTK_MENU (popup); + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); - if (priv->popup_window) - { - gtk_widget_destroy (priv->popup_window); - priv->popup_window = NULL; - } + gtk_style_context_get (context, state, + "background-color", &color, + NULL); - priv->popup_widget = popup; + gtk_cell_view_set_background_rgba (GTK_CELL_VIEW (priv->cell_view), color); + gdk_rgba_free (color); + } + } - /* Note that we connect to show/hide on the toplevel, not the - * menu itself, since the menu is not shown/hidden when it is - * popped up while torn-off. - */ - g_signal_connect (menu->priv->toplevel, "show", - G_CALLBACK (gtk_combo_box_menu_show), combo_box); - g_signal_connect (menu->priv->toplevel, "hide", - G_CALLBACK (gtk_combo_box_menu_hide), combo_box); - - gtk_menu_attach_to_widget (menu, GTK_WIDGET (combo_box), gtk_combo_box_detacher); - } - else - { - if (!priv->popup_window) - { - GtkWidget *toplevel; - - priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP); - gtk_widget_set_name (priv->popup_window, "gtk-combobox-popup-window"); - - gtk_window_set_type_hint (GTK_WINDOW (priv->popup_window), - GDK_WINDOW_TYPE_HINT_COMBO); - - g_signal_connect (GTK_WINDOW (priv->popup_window),"show", - G_CALLBACK (gtk_combo_box_child_show), - combo_box); - g_signal_connect (GTK_WINDOW (priv->popup_window),"hide", - G_CALLBACK (gtk_combo_box_child_hide), - combo_box); - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo_box)); - if (GTK_IS_WINDOW (toplevel)) - { - gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), - GTK_WINDOW (priv->popup_window)); - gtk_window_set_transient_for (GTK_WINDOW (priv->popup_window), - GTK_WINDOW (toplevel)); - } - - gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE); - gtk_window_set_screen (GTK_WINDOW (priv->popup_window), - gtk_widget_get_screen (GTK_WIDGET (combo_box))); - - priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); - - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - GTK_POLICY_NEVER, - GTK_POLICY_NEVER); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window), - GTK_SHADOW_IN); - - gtk_widget_show (priv->scrolled_window); - - gtk_container_add (GTK_CONTAINER (priv->popup_window), - priv->scrolled_window); - } - - gtk_container_add (GTK_CONTAINER (priv->scrolled_window), - popup); - - gtk_widget_show (popup); - g_object_ref (popup); - priv->popup_widget = popup; - } + gtk_widget_queue_draw (widget); } static void -get_widget_border (GtkWidget *widget, - GtkBorder *border) +gtk_combo_box_button_state_changed (GtkWidget *widget, + GtkStateType previous, + gpointer data) { - GtkStyleContext *context; - GtkBorder *border_width; - - context = gtk_widget_get_style_context (widget); + GtkComboBox *combo_box = GTK_COMBO_BOX (data); + GtkComboBoxPrivate *priv = combo_box->priv; - gtk_style_context_get (context, - gtk_widget_get_state_flags (widget), - "border-width", &border_width, - NULL); + if (gtk_widget_get_realized (widget)) + { + if (!priv->tree_view && priv->cell_view) + { + if ((gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE) != + (gtk_widget_get_state (priv->cell_view) == GTK_STATE_INSENSITIVE)) + gtk_widget_set_sensitive (priv->cell_view, gtk_widget_get_sensitive (widget)); + + gtk_widget_set_state (priv->cell_view, + gtk_widget_get_state (widget)); + } + } - *border = *border_width; - gtk_border_free (border_width); + gtk_widget_queue_draw (widget); } static void -gtk_combo_box_menu_position_below (GtkMenu *menu, - gint *x, - gint *y, - gint *push_in, - gpointer user_data) +gtk_combo_box_check_appearance (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkAllocation child_allocation; - gint sx, sy; - GtkWidget *child; - GtkRequisition req; - GdkScreen *screen; - gint monitor_num; - GdkRectangle monitor; - GtkBorder border; - - /* FIXME: is using the size request here broken? */ - child = gtk_bin_get_child (GTK_BIN (combo_box)); - - sx = sy = 0; + GtkComboBoxPrivate *priv = combo_box->priv; + gboolean appears_as_list; - gtk_widget_get_allocation (child, &child_allocation); + /* if wrap_width > 0, then we are in grid-mode and forced to use + * unix style + */ + if (priv->wrap_width) + appears_as_list = FALSE; + else + gtk_widget_style_get (GTK_WIDGET (combo_box), + "appears-as-list", &appears_as_list, + NULL); - if (!gtk_widget_get_has_window (child)) + if (appears_as_list) { - sx += child_allocation.x; - sy += child_allocation.y; - } - - gdk_window_get_root_coords (gtk_widget_get_window (child), - sx, sy, &sx, &sy); - get_widget_border (GTK_WIDGET (combo_box), &border); - sx -= border.left; - - if (combo_box->priv->popup_fixed_width) - gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL); - else - gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req); + /* Destroy all the menu mode widgets, if they exist. */ + if (GTK_IS_MENU (priv->popup_widget)) + gtk_combo_box_menu_destroy (combo_box); - if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_LTR) - *x = sx; + /* Create the list mode widgets, if they don't already exist. */ + if (!GTK_IS_TREE_VIEW (priv->tree_view)) + gtk_combo_box_list_setup (combo_box); + } else - *x = sx + child_allocation.width - req.width; - *y = sy; + { + /* Destroy all the list mode widgets, if they exist. */ + if (GTK_IS_TREE_VIEW (priv->tree_view)) + gtk_combo_box_list_destroy (combo_box); - screen = gtk_widget_get_screen (GTK_WIDGET (combo_box)); - monitor_num = gdk_screen_get_monitor_at_window (screen, - gtk_widget_get_window (GTK_WIDGET (combo_box))); - gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); - - if (*x < monitor.x) - *x = monitor.x; - else if (*x + req.width > monitor.x + monitor.width) - *x = monitor.x + monitor.width - req.width; - - if (monitor.y + monitor.height - *y - child_allocation.height >= req.height) - *y += child_allocation.height; - else if (*y - monitor.y >= req.height) - *y -= req.height; - else if (monitor.y + monitor.height - *y - child_allocation.height > *y - monitor.y) - *y += child_allocation.height; - else - *y -= req.height; + /* Create the menu mode widgets, if they don't already exist. */ + if (!GTK_IS_MENU (priv->popup_widget)) + gtk_combo_box_menu_setup (combo_box, TRUE); + } - *push_in = FALSE; + gtk_widget_style_get (GTK_WIDGET (combo_box), + "shadow-type", &priv->shadow_type, + NULL); } static void -gtk_combo_box_menu_position_over (GtkMenu *menu, - gint *x, - gint *y, - gboolean *push_in, - gpointer user_data) +gtk_combo_box_style_updated (GtkWidget *widget) { - GtkComboBox *combo_box; - GtkWidget *active; - GtkWidget *child; - GtkWidget *widget; - GtkAllocation allocation; - GtkAllocation child_allocation; - GList *children; - gint screen_width; - gint menu_xpos; - gint menu_ypos; - gint menu_width; - - combo_box = GTK_COMBO_BOX (user_data); - widget = GTK_WIDGET (combo_box); - - active = gtk_menu_get_active (GTK_MENU (combo_box->priv->popup_widget)); - - gtk_widget_get_allocation (widget, &allocation); - - menu_xpos = allocation.x; - menu_ypos = allocation.y + allocation.height / 2 - 2; + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + GtkComboBoxPrivate *priv = combo_box->priv; + GtkWidget *child; - if (combo_box->priv->popup_fixed_width) - gtk_widget_get_preferred_width (GTK_WIDGET (menu), &menu_width, NULL); - else - gtk_widget_get_preferred_width (GTK_WIDGET (menu), NULL, &menu_width); + gtk_combo_box_check_appearance (combo_box); - if (active != NULL) + if (priv->tree_view && priv->cell_view) { - gtk_widget_get_allocation (active, &child_allocation); - menu_ypos -= child_allocation.height / 2; - } + GtkStyleContext *context; + GdkRGBA *color; - children = GTK_MENU_SHELL (combo_box->priv->popup_widget)->priv->children; - while (children) - { - child = children->data; + context = gtk_widget_get_style_context (widget); + gtk_style_context_get (context, 0, + "background-color", &color, + NULL); - if (active == child) - break; + gtk_cell_view_set_background_rgba (GTK_CELL_VIEW (priv->cell_view), color); + gdk_rgba_free (color); + } - if (gtk_widget_get_visible (child)) - { - gtk_widget_get_allocation (child, &child_allocation); + child = gtk_bin_get_child (GTK_BIN (combo_box)); + if (GTK_IS_ENTRY (child)) + g_object_set (child, "shadow-type", + GTK_SHADOW_NONE == priv->shadow_type ? + GTK_SHADOW_IN : GTK_SHADOW_NONE, NULL); +} - menu_ypos -= child_allocation.height; - } +static void +gtk_combo_box_destroy (GtkWidget *widget) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - children = children->next; + if (combo_box->priv->popup_idle_id > 0) + { + g_source_remove (combo_box->priv->popup_idle_id); + combo_box->priv->popup_idle_id = 0; } - if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) - menu_xpos = menu_xpos + allocation.width - menu_width; + gtk_combo_box_popdown (combo_box); - gdk_window_get_root_coords (gtk_widget_get_window (widget), - menu_xpos, menu_ypos, - &menu_xpos, &menu_ypos); + if (combo_box->priv->row_separator_destroy) + combo_box->priv->row_separator_destroy (combo_box->priv->row_separator_data); - /* Clamp the position on screen */ - screen_width = gdk_screen_get_width (gtk_widget_get_screen (widget)); - - if (menu_xpos < 0) - menu_xpos = 0; - else if ((menu_xpos + menu_width) > screen_width) - menu_xpos -= ((menu_xpos + menu_width) - screen_width); + combo_box->priv->row_separator_func = NULL; + combo_box->priv->row_separator_data = NULL; + combo_box->priv->row_separator_destroy = NULL; - *x = menu_xpos; - *y = menu_ypos; + GTK_WIDGET_CLASS (gtk_combo_box_parent_class)->destroy (widget); + combo_box->priv->cell_view = NULL; +} - *push_in = TRUE; -} -static void -gtk_combo_box_menu_position (GtkMenu *menu, - gint *x, - gint *y, - gint *push_in, - gpointer user_data) +static void +gtk_combo_box_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *menu_item; + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + GtkComboBoxPrivate *priv = combo_box->priv; + gint focus_width, focus_pad; + gint font_size, arrow_size; + PangoContext *context; + PangoFontMetrics *metrics; + PangoFontDescription *font_desc; + GtkWidget *child; + gint minimum_width, natural_width; + gint child_min, child_nat; + GtkStyleContext *style_context; + GtkStateFlags state; - if (priv->wrap_width > 0 || priv->cell_view == NULL) - gtk_combo_box_menu_position_below (menu, x, y, push_in, user_data); - else - { - /* FIXME handle nested menus better */ - menu_item = gtk_menu_get_active (GTK_MENU (priv->popup_widget)); - if (menu_item) - gtk_menu_shell_select_item (GTK_MENU_SHELL (priv->popup_widget), - menu_item); + child = gtk_bin_get_child (GTK_BIN (widget)); + + /* common */ + gtk_widget_get_preferred_width (child, &child_min, &child_nat); - gtk_combo_box_menu_position_over (menu, x, y, push_in, user_data); - } + gtk_widget_style_get (GTK_WIDGET (widget), + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + "arrow-size", &arrow_size, + NULL); - if (!gtk_widget_get_visible (GTK_MENU (priv->popup_widget)->priv->toplevel)) - gtk_window_set_type_hint (GTK_WINDOW (GTK_MENU (priv->popup_widget)->priv->toplevel), - GDK_WINDOW_TYPE_HINT_COMBO); -} + style_context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); -static void -gtk_combo_box_list_position (GtkComboBox *combo_box, - gint *x, - gint *y, - gint *width, - gint *height) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkAllocation allocation; - GdkScreen *screen; - gint monitor_num; - GdkRectangle monitor; - GtkRequisition popup_req; - GtkPolicyType hpolicy, vpolicy; - GdkWindow *window; + gtk_style_context_get (style_context, state, + "font", &font_desc, + NULL); - /* under windows, the drop down list is as wide as the combo box itself. - see bug #340204 */ - GtkWidget *widget = GTK_WIDGET (combo_box); + context = gtk_widget_get_pango_context (GTK_WIDGET (widget)); + metrics = pango_context_get_metrics (context, font_desc, + pango_context_get_language (context)); + font_size = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + pango_font_metrics_unref (metrics); + pango_font_description_free (font_desc); - *x = *y = 0; + arrow_size = MAX (arrow_size, font_size); - gtk_widget_get_allocation (widget, &allocation); + gtk_widget_set_size_request (priv->arrow, arrow_size, arrow_size); - if (!gtk_widget_get_has_window (widget)) + if (!priv->tree_view) { - *x += allocation.x; - *y += allocation.y; - } - - window = gtk_widget_get_window (widget); + /* menu mode */ + if (priv->cell_view) + { + gint sep_width, arrow_width; + gint border_width, xthickness, xpad; - gdk_window_get_root_coords (gtk_widget_get_window (widget), - *x, *y, x, y); + border_width = gtk_container_get_border_width (GTK_CONTAINER (combo_box)); + xthickness = get_widget_border_thickness (priv->button); - *width = allocation.width; + gtk_widget_get_preferred_width (priv->separator, &sep_width, NULL); + gtk_widget_get_preferred_width (priv->arrow, &arrow_width, NULL); - hpolicy = vpolicy = GTK_POLICY_NEVER; - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - hpolicy, vpolicy); + xpad = 2*(border_width + xthickness + focus_width + focus_pad); - /* XXX This set_size_request call is part of the hack outlined below and can - * go away once height-for-width is implemented on treeviews. */ - gtk_widget_set_size_request (priv->tree_view, -1, -1); + minimum_width = child_min + sep_width + arrow_width + xpad; + natural_width = child_nat + sep_width + arrow_width + xpad; + } + else + { + gint but_width, but_nat_width; - if (combo_box->priv->popup_fixed_width) - { - gtk_widget_get_preferred_size (priv->scrolled_window, &popup_req, NULL); + gtk_widget_get_preferred_width (priv->button, + &but_width, &but_nat_width); - if (popup_req.width > *width) - { - hpolicy = GTK_POLICY_ALWAYS; - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - hpolicy, vpolicy); - } + minimum_width = child_min + but_width; + natural_width = child_nat + but_nat_width; + } } else { - gtk_combo_box_remeasure (combo_box); - - if (priv->natural_width > *width) - { - hpolicy = GTK_POLICY_NEVER; - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - hpolicy, vpolicy); - - - /* XXX Currently we set the size-request on the internal treeview to be - * the natural width of the cells, this hack can go away once our - * treeview does height-for-width properly (i.e. just adjust *width - * here to be the natural width request of the scrolled-window). - * - * I can't tell why the magic number 5 is needed here (i.e. without it - * treeviews are left ellipsizing) , however it this all should be - * removed with height-for-width treeviews. - */ - gtk_widget_set_size_request (priv->tree_view, priv->natural_width + 5, -1); - gtk_widget_get_preferred_size (priv->scrolled_window, NULL, &popup_req); - - *width = popup_req.width; - } - } + /* list mode */ + gint button_width, button_nat_width; - *height = popup_req.height; + /* sample + frame */ + minimum_width = child_min; + natural_width = child_nat; - screen = gtk_widget_get_screen (GTK_WIDGET (combo_box)); - monitor_num = gdk_screen_get_monitor_at_window (screen, window); - gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + minimum_width += 2 * focus_width; + natural_width += 2 * focus_width; + + if (priv->cell_view_frame) + { + if (priv->has_frame) + { + gint border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->cell_view_frame)); + gint xpad = 2 * (border_width + get_widget_border_thickness (priv->cell_view_frame)); - if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_RTL) - *x = *x + allocation.width - *width; + minimum_width += xpad; + natural_width += xpad; + } + } - if (*x < monitor.x) - *x = monitor.x; - else if (*x + *width > monitor.x + monitor.width) - *x = monitor.x + monitor.width - *width; - - if (*y + allocation.height + *height <= monitor.y + monitor.height) - *y += allocation.height; - else if (*y - *height >= monitor.y) - *y -= *height; - else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y) - { - *y += allocation.height; - *height = monitor.y + monitor.height - *y; - } - else - { - *height = *y - monitor.y; - *y = monitor.y; - } + /* the button */ + gtk_widget_get_preferred_width (priv->button, + &button_width, &button_nat_width); - if (popup_req.height > *height) - { - vpolicy = GTK_POLICY_ALWAYS; - - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - hpolicy, vpolicy); + minimum_width += button_width; + natural_width += button_nat_width; } -} - -static gboolean -cell_view_is_sensitive (GtkCellView *cell_view) -{ - GList *cells, *list; - gboolean sensitive; - - cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (cell_view)); - sensitive = FALSE; - for (list = cells; list; list = list->next) + if (GTK_SHADOW_NONE != priv->shadow_type) { - g_object_get (list->data, "sensitive", &sensitive, NULL); - - if (sensitive) - break; - } - g_list_free (cells); - - return sensitive; -} - -static gboolean -tree_column_row_is_sensitive (GtkComboBox *combo_box, - GtkTreeIter *iter) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GList *cells, *list; - gboolean sensitive; - - if (!priv->column) - return TRUE; + gint thickness; - if (priv->row_separator_func) - { - if (priv->row_separator_func (priv->model, iter, - priv->row_separator_data)) - return FALSE; + thickness = get_widget_border_thickness (GTK_WIDGET (widget)); + minimum_width += 2 * thickness; + natural_width += 2 * thickness; } - gtk_tree_view_column_cell_set_cell_data (priv->column, - priv->model, - iter, FALSE, FALSE); + if (minimum_size) + *minimum_size = minimum_width; - cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->column)); + if (natural_size) + *natural_size = natural_width; +} - sensitive = FALSE; - for (list = cells; list; list = list->next) - { - g_object_get (list->data, "sensitive", &sensitive, NULL); - - if (sensitive) - break; - } - g_list_free (cells); +static void +gtk_combo_box_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gint min_width; - return sensitive; + /* Combo box is height-for-width only + * (so we always just reserve enough height for the minimum width) */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_width, minimum_size, natural_size); } static void -update_menu_sensitivity (GtkComboBox *combo_box, - GtkWidget *menu) +gtk_combo_box_get_preferred_width_for_height (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size) { - GtkComboBoxPrivate *priv = combo_box->priv; - GList *children, *child; - GtkWidget *item, *submenu, *separator; - GtkWidget *cell_view; - gboolean sensitive; - - if (!priv->model) - return; + /* Combo box is height-for-width only + * (so we assume we always reserved enough height for the minimum width) */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_size, natural_size); +} - children = gtk_container_get_children (GTK_CONTAINER (menu)); - for (child = children; child; child = child->next) - { - item = GTK_WIDGET (child->data); - cell_view = gtk_bin_get_child (GTK_BIN (item)); +static void +gtk_combo_box_get_preferred_height_for_width (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + GtkComboBoxPrivate *priv = combo_box->priv; + gint focus_width, focus_pad; + gint min_height, nat_height; + gint size; + GtkWidget *child; - if (!GTK_IS_CELL_VIEW (cell_view)) - continue; - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)); - if (submenu != NULL) - { - gtk_widget_set_sensitive (item, TRUE); - update_menu_sensitivity (combo_box, submenu); - } - else - { - sensitive = cell_view_is_sensitive (GTK_CELL_VIEW (cell_view)); + gtk_widget_style_get (GTK_WIDGET (widget), + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); - if (menu != priv->popup_widget && child == children) - { - separator = GTK_WIDGET (child->next->data); - g_object_set (item, "visible", sensitive, NULL); - g_object_set (separator, "visible", sensitive, NULL); - } - else - gtk_widget_set_sensitive (item, sensitive); - } - } + size = avail_size; - g_list_free (children); -} + child = gtk_bin_get_child (GTK_BIN (widget)); -static void -gtk_combo_box_menu_popup (GtkComboBox *combo_box, - guint button, - guint32 activate_time) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreePath *path; - gint active_item; - gint width, min_width, nat_width; - - update_menu_sensitivity (combo_box, priv->popup_widget); + if (GTK_SHADOW_NONE != priv->shadow_type) + size -= get_widget_border_thickness (widget); - active_item = -1; - if (gtk_tree_row_reference_valid (priv->active_row)) + if (!priv->tree_view) { - path = gtk_tree_row_reference_get_path (priv->active_row); - active_item = gtk_tree_path_get_indices (path)[0]; - gtk_tree_path_free (path); - - if (priv->add_tearoffs) - active_item++; - } + /* menu mode */ + if (priv->cell_view) + { + /* calculate x/y padding and separator/arrow size */ + gint sep_width, arrow_width, sep_height, arrow_height; + gint border_width, xthickness, ythickness, xpad, ypad; - /* FIXME handle nested menus better */ - gtk_menu_set_active (GTK_MENU (priv->popup_widget), active_item); - - if (priv->wrap_width == 0) - { - GtkAllocation allocation; + border_width = gtk_container_get_border_width (GTK_CONTAINER (combo_box)); + xthickness = ythickness = get_widget_border_thickness (priv->button); - gtk_widget_get_allocation (GTK_WIDGET (combo_box), &allocation); - width = allocation.width; - gtk_widget_set_size_request (priv->popup_widget, -1, -1); - gtk_widget_get_preferred_width (priv->popup_widget, &min_width, &nat_width); + gtk_widget_get_preferred_width (priv->separator, &sep_width, NULL); + gtk_widget_get_preferred_width (priv->arrow, &arrow_width, NULL); + gtk_widget_get_preferred_height_for_width (priv->separator, + sep_width, &sep_height, NULL); + gtk_widget_get_preferred_height_for_width (priv->arrow, + arrow_width, &arrow_height, NULL); - if (combo_box->priv->popup_fixed_width) - width = MAX (width, min_width); + xpad = 2*(border_width + xthickness + focus_width + focus_pad); + ypad = 2*(border_width + ythickness + focus_width + focus_pad); + + size -= sep_width + arrow_width + xpad; + + /* Get height-for-width of the child widget, usually a GtkCellArea calculating + * and fitting the whole treemodel */ + gtk_widget_get_preferred_height_for_width (child, size, &min_height, &nat_height); + + arrow_height = MAX (arrow_height, sep_height); + min_height = MAX (min_height, arrow_height); + nat_height = MAX (nat_height, arrow_height); + + min_height += ypad; + nat_height += ypad; + } else - width = MAX (width, nat_width); + { + /* there is a custom child widget inside (no priv->cell_view) */ + gint but_width, but_height; - gtk_widget_set_size_request (priv->popup_widget, width, -1); + gtk_widget_get_preferred_width (priv->button, &but_width, NULL); + gtk_widget_get_preferred_height_for_width (priv->button, + but_width, &but_height, NULL); + + size -= but_width; + + /* Get height-for-width of the child widget, usually a GtkCellArea calculating + * and fitting the whole treemodel */ + gtk_widget_get_preferred_height_for_width (child, size, &min_height, &nat_height); + + min_height = MAX (min_height, but_height); + nat_height = MAX (nat_height, but_height); + } } - - gtk_menu_popup (GTK_MENU (priv->popup_widget), - NULL, NULL, - gtk_combo_box_menu_position, combo_box, - button, activate_time); -} + else + { + /* list mode */ + gint but_width, but_height; + gint xpad = 0, ypad = 0; -static gboolean -popup_grab_on_window (GdkWindow *window, - GdkDevice *keyboard, - GdkDevice *pointer, - guint32 activate_time) -{ - if (keyboard && - gdk_device_grab (keyboard, window, - GDK_OWNERSHIP_WINDOW, TRUE, - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, - NULL, activate_time) != GDK_GRAB_SUCCESS) - return FALSE; + gtk_widget_get_preferred_width (priv->button, &but_width, NULL); + gtk_widget_get_preferred_height_for_width (priv->button, + but_width, &but_height, NULL); + + if (priv->cell_view_frame && priv->has_frame) + { + gint border_width; + gint thickness; - if (pointer && - gdk_device_grab (pointer, window, - GDK_OWNERSHIP_WINDOW, TRUE, - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK, - NULL, activate_time) != GDK_GRAB_SUCCESS) + border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->cell_view_frame)); + thickness = get_widget_border_thickness (GTK_WIDGET (priv->cell_view_frame)); + + xpad = 2 * (border_width + thickness); + ypad = 2 * (border_width + thickness); + } + + size -= but_width; + size -= 2 * focus_width; + size -= xpad; + + /* Get height-for-width of the child widget, usually a GtkCellArea calculating + * and fitting the whole treemodel */ + gtk_widget_get_preferred_height_for_width (child, size, &min_height, &nat_height); + + min_height = MAX (min_height, but_height); + nat_height = MAX (nat_height, but_height); + + min_height += ypad; + nat_height += ypad; + } + + if (GTK_SHADOW_NONE != priv->shadow_type) { - if (keyboard) - gdk_device_ungrab (keyboard, activate_time); + gint thickness; - return FALSE; + thickness = get_widget_border_thickness (widget); + min_height += 2 * thickness; + nat_height += 2 * thickness; } - return TRUE; + if (minimum_size) + *minimum_size = min_height; + + if (natural_size) + *natural_size = nat_height; } -/** - * gtk_combo_box_popup: - * @combo_box: a #GtkComboBox - * - * Pops up the menu or dropdown list of @combo_box. - * - * This function is mostly intended for use by accessibility technologies; - * applications should have little use for it. - * - * Since: 2.4 - */ -void -gtk_combo_box_popup (GtkComboBox *combo_box) +#define GTK_COMBO_BOX_SIZE_ALLOCATE_BUTTON \ + gtk_widget_get_preferred_size (combo_box->priv->button, \ + &req, NULL); \ + \ + if (is_rtl) \ + child.x = allocation->x + shadow_width; \ + else \ + child.x = allocation->x + allocation->width - req.width - shadow_width; \ + \ + child.y = allocation->y + shadow_height; \ + child.width = req.width; \ + child.height = allocation->height - 2 * shadow_height; \ + child.width = MAX (1, child.width); \ + child.height = MAX (1, child.height); \ + \ + gtk_widget_size_allocate (combo_box->priv->button, &child); + +static gint +get_widget_border_thickness (GtkWidget *widget) { - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + GtkStyleContext *context; + gint thickness; - g_signal_emit (combo_box, combo_box_signals[POPUP], 0); + context = gtk_widget_get_style_context (widget); + + gtk_style_context_get (context, + gtk_widget_get_state_flags (widget), + "border-width", &thickness, + NULL); + return thickness; } -/** - * gtk_combo_box_popup_for_device: - * @combo_box: a #GtkComboBox - * @device: a #GdkDevice - * - * Pops up the menu or dropdown list of @combo_box, the popup window - * will be grabbed so only @device and its associated pointer/keyboard - * are the only #GdkDevices able to send events to it. - * - * Since: 3.0 - **/ -void -gtk_combo_box_popup_for_device (GtkComboBox *combo_box, - GdkDevice *device) +static void +gtk_combo_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) { + GtkComboBox *combo_box = GTK_COMBO_BOX (widget); GtkComboBoxPrivate *priv = combo_box->priv; - gint x, y, width, height; - GtkTreePath *path = NULL, *ppath; - GtkWidget *toplevel; - GdkDevice *keyboard, *pointer; - guint32 time; - - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - g_return_if_fail (GDK_IS_DEVICE (device)); - - if (!gtk_widget_get_realized (GTK_WIDGET (combo_box))) - return; - - if (priv->popup_window && gtk_widget_get_mapped (priv->popup_window)) - return; + GtkWidget *child_widget; + gint shadow_width, shadow_height; + gint focus_width, focus_pad; + GtkAllocation child; + GtkRequisition req; + gboolean is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; - if (priv->grab_pointer && priv->grab_keyboard) - return; + gtk_widget_set_allocation (widget, allocation); + child_widget = gtk_bin_get_child (GTK_BIN (widget)); - time = gtk_get_current_event_time (); + gtk_widget_style_get (widget, + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); - if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) - { - keyboard = device; - pointer = gdk_device_get_associated_device (device); - } + if (GTK_SHADOW_NONE != priv->shadow_type) + shadow_width = shadow_height = get_widget_border_thickness (widget); else { - pointer = device; - keyboard = gdk_device_get_associated_device (device); + shadow_width = 0; + shadow_height = 0; } - if (GTK_IS_MENU (priv->popup_widget)) + if (!priv->tree_view) { - gtk_combo_box_menu_popup (combo_box, - priv->activate_button, - priv->activate_time); - return; - } - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo_box)); - if (GTK_IS_WINDOW (toplevel)) - gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), - GTK_WINDOW (priv->popup_window)); - - gtk_widget_show_all (priv->scrolled_window); - gtk_combo_box_list_position (combo_box, &x, &y, &width, &height); - - gtk_widget_set_size_request (priv->popup_window, width, height); - gtk_window_move (GTK_WINDOW (priv->popup_window), x, y); - - if (gtk_tree_row_reference_valid (priv->active_row)) - { - path = gtk_tree_row_reference_get_path (priv->active_row); - ppath = gtk_tree_path_copy (path); - if (gtk_tree_path_up (ppath)) - gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->tree_view), - ppath); - gtk_tree_path_free (ppath); - } - gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view), - TRUE); - - /* popup */ - gtk_widget_show (priv->popup_window); - - if (path) - { - gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->tree_view), - path, NULL, FALSE); - gtk_tree_path_free (path); - } - - gtk_widget_grab_focus (priv->popup_window); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), - TRUE); - - if (!gtk_widget_has_focus (priv->tree_view)) - gtk_widget_grab_focus (priv->tree_view); - - if (!popup_grab_on_window (gtk_widget_get_window (priv->popup_window), - keyboard, pointer, time)) - { - gtk_widget_hide (priv->popup_window); - return; - } - - gtk_device_grab_add (priv->popup_window, pointer, TRUE); - priv->grab_pointer = pointer; - priv->grab_keyboard = keyboard; -} - -static void -gtk_combo_box_real_popup (GtkComboBox *combo_box) -{ - GdkDevice *device; - - device = gtk_get_current_event_device (); - - if (!device) - { - GdkDeviceManager *device_manager; - GdkDisplay *display; - GList *devices; - - display = gtk_widget_get_display (GTK_WIDGET (combo_box)); - device_manager = gdk_display_get_device_manager (display); - - /* No device was set, pick the first master device */ - devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); - device = devices->data; - g_list_free (devices); - } - - gtk_combo_box_popup_for_device (combo_box, device); -} - -static gboolean -gtk_combo_box_real_popdown (GtkComboBox *combo_box) -{ - if (combo_box->priv->popup_shown) - { - gtk_combo_box_popdown (combo_box); - return TRUE; - } - - return FALSE; -} - -/** - * gtk_combo_box_popdown: - * @combo_box: a #GtkComboBox - * - * Hides the menu or dropdown list of @combo_box. - * - * This function is mostly intended for use by accessibility technologies; - * applications should have little use for it. - * - * Since: 2.4 - */ -void -gtk_combo_box_popdown (GtkComboBox *combo_box) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - - if (GTK_IS_MENU (priv->popup_widget)) - { - gtk_menu_popdown (GTK_MENU (priv->popup_widget)); - return; - } - - if (!gtk_widget_get_realized (GTK_WIDGET (combo_box))) - return; - - gtk_device_grab_remove (priv->popup_window, priv->grab_pointer); - gtk_widget_hide (priv->popup_window); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), - FALSE); - - priv->grab_pointer = NULL; - priv->grab_keyboard = NULL; -} - -static void -gtk_combo_box_update_requested_width (GtkComboBox *combo_box, - GtkTreePath *path) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - gint padding, min_width, nat_width; - - if (priv->cell_view) - gtk_widget_style_get (priv->cell_view, - "focus-line-width", &padding, - NULL); - else - padding = 0; - - /* add some pixels for good measure */ - padding += BONUS_PADDING; - - if (priv->cell_view) - gtk_cell_view_get_desired_width_of_row (GTK_CELL_VIEW (priv->cell_view), - path, &min_width, &nat_width); - else - min_width = nat_width = 0; - - min_width += padding; - nat_width += padding; - - if (min_width > priv->minimum_width || nat_width > priv->natural_width) - { - priv->minimum_width = MAX (priv->minimum_width, min_width); - priv->natural_width = MAX (priv->natural_width, nat_width); - - if (priv->cell_view) - { - gtk_widget_set_size_request (priv->cell_view, min_width, -1); - gtk_widget_queue_resize (priv->cell_view); - } - } -} - -#define GTK_COMBO_BOX_SIZE_ALLOCATE_BUTTON \ - gtk_widget_get_preferred_size (combo_box->priv->button, \ - &req, NULL); \ - \ - if (is_rtl) \ - child.x = allocation->x + border.right; \ - else \ - child.x = allocation->x + allocation->width - req.width - border.left; \ - \ - child.y = allocation->y + border.top; \ - child.width = req.width; \ - child.height = allocation->height - (border.top + border.bottom); \ - child.width = MAX (1, child.width); \ - child.height = MAX (1, child.height); \ - \ - gtk_widget_size_allocate (combo_box->priv->button, &child); - -static void -gtk_combo_box_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *child_widget; - gint focus_width, focus_pad; - GtkAllocation child; - GtkRequisition req; - GtkBorder border; - gboolean is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; - - gtk_widget_set_allocation (widget, allocation); - child_widget = gtk_bin_get_child (GTK_BIN (widget)); - get_widget_border (widget, &border); - - gtk_widget_style_get (widget, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - NULL); - - if (!priv->tree_view) - { - if (priv->cell_view) - { - GtkBorder button_border; - gint width; - guint border_width; + if (priv->cell_view) + { + gint xthickness, ythickness; + gint width; + guint border_width; /* menu mode */ - allocation->x += border.left; - allocation->y += border.top; - allocation->width -= border.left + border.right; - allocation->height -= border.top + border.bottom; + allocation->x += shadow_width; + allocation->y += shadow_height; + allocation->width -= 2 * shadow_width; + allocation->height -= 2 * shadow_height; gtk_widget_size_allocate (priv->button, allocation); /* set some things ready */ border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->button)); - get_widget_border (priv->button, &button_border); + xthickness = ythickness = get_widget_border_thickness (priv->button); child.x = allocation->x; child.y = allocation->y; @@ -2551,12 +2178,10 @@ gtk_combo_box_size_allocate (GtkWidget *widget, if (!priv->is_cell_renderer) { - child.x += border_width + button_border.left + focus_width + focus_pad; - child.y += border_width + button_border.top + focus_width + focus_pad; - width -= (2 * (border_width + focus_width + focus_pad)) + - button_border.left + button_border.right; - child.height -= (2 * (border_width + focus_width + focus_pad)) + - button_border.top + button_border.bottom; + child.x += border_width + xthickness + focus_width + focus_pad; + child.y += border_width + ythickness + focus_width + focus_pad; + width -= 2 * (child.x - allocation->x); + child.height -= 2 * (child.y - allocation->y); } @@ -2582,14 +2207,14 @@ gtk_combo_box_size_allocate (GtkWidget *widget, { child.x += req.width; child.width = allocation->x + allocation->width - - (border_width + button_border.right + focus_width + focus_pad) + - (border_width + xthickness + focus_width + focus_pad) - child.x; } else { child.width = child.x; child.x = allocation->x - + border_width + button_border.left + focus_width + focus_pad; + + border_width + xthickness + focus_width + focus_pad; child.width -= child.x; } @@ -2627,11 +2252,11 @@ gtk_combo_box_size_allocate (GtkWidget *widget, GTK_COMBO_BOX_SIZE_ALLOCATE_BUTTON if (is_rtl) - child.x = allocation->x + req.width + border.right; + child.x = allocation->x + req.width + shadow_width; else - child.x = allocation->x + border.left; - child.y = allocation->y + border.top; - child.width = allocation->width - req.width - (border.left + border.right); + child.x = allocation->x + shadow_width; + child.y = allocation->y + shadow_height; + child.width = allocation->width - req.width - 2 * shadow_width; child.width = MAX (1, child.width); child.height = MAX (1, child.height); gtk_widget_size_allocate (child_widget, &child); @@ -2640,7 +2265,11 @@ gtk_combo_box_size_allocate (GtkWidget *widget, else { /* list mode */ + + /* Combobox thickness + border-width */ guint border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + int delta_x = shadow_width + border_width; + int delta_y = shadow_height + border_width; /* button */ GTK_COMBO_BOX_SIZE_ALLOCATE_BUTTON @@ -2657,32 +2286,30 @@ gtk_combo_box_size_allocate (GtkWidget *widget, if (priv->cell_view_frame) { - child.x += border.left + border_width; - child.y += border.top + border_width; - child.width = MAX (1, child.width - (2 * border_width) - (border.left + border.right)); - child.height = MAX (1, child.height - (2 * border_width) - (border.top + border.bottom)); + child.x += delta_x; + child.y += delta_y; + child.width = MAX (1, child.width - delta_x * 2); + child.height = MAX (1, child.height - delta_y * 2); gtk_widget_size_allocate (priv->cell_view_frame, &child); /* the sample */ if (priv->has_frame) { - GtkBorder frame_border; - border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->cell_view_frame)); - get_widget_border (priv->cell_view_frame, &frame_border); + delta_x = delta_y = border_width + get_widget_border_thickness (priv->cell_view_frame); - child.x += border_width + frame_border.left; - child.y += border_width + frame_border.right; - child.width -= (2 * border_width) + frame_border.left + frame_border.right; - child.height -= (2 * border_width) + frame_border.top + frame_border.bottom; + child.x += delta_x; + child.y += delta_y; + child.width -= delta_x * 2; + child.height -= delta_y * 2; } } else { - child.x += border.left + border_width; - child.y += border.top + border_width; - child.width -= (2 * border_width) + border.left + border.right; - child.height -= (2 * border_width) + border.top + border.bottom; + child.x += delta_x; + child.y += delta_y; + child.width -= delta_x * 2; + child.height -= delta_y * 2; } if (gtk_widget_get_visible (priv->popup_window)) @@ -2703,57 +2330,155 @@ gtk_combo_box_size_allocate (GtkWidget *widget, #undef GTK_COMBO_BOX_ALLOCATE_BUTTON +/****************************************************** + * GtkContainerClass * + ******************************************************/ + static void -gtk_combo_box_unset_model (GtkComboBox *combo_box) +gtk_combo_box_add (GtkContainer *container, + GtkWidget *widget) { + GtkComboBox *combo_box = GTK_COMBO_BOX (container); GtkComboBoxPrivate *priv = combo_box->priv; - if (priv->model) + if (priv->has_entry && !GTK_IS_ENTRY (widget)) { - g_signal_handler_disconnect (priv->model, - priv->inserted_id); - g_signal_handler_disconnect (priv->model, - priv->deleted_id); - g_signal_handler_disconnect (priv->model, - priv->reordered_id); - g_signal_handler_disconnect (priv->model, - priv->changed_id); + g_warning ("Attempting to add a widget with type %s to a GtkComboBox that needs an entry " + "(need an instance of GtkEntry or of a subclass)", + G_OBJECT_TYPE_NAME (widget)); + return; } - /* menu mode */ - if (!priv->tree_view) + if (priv->cell_view && + gtk_widget_get_parent (priv->cell_view)) { - if (priv->popup_widget) - gtk_container_foreach (GTK_CONTAINER (priv->popup_widget), - (GtkCallback)gtk_widget_destroy, NULL); + gtk_container_remove (container, priv->cell_view); + gtk_widget_queue_resize (GTK_WIDGET (container)); } - if (priv->model) + gtk_widget_set_parent (widget, GTK_WIDGET (container)); + _gtk_bin_set_child (GTK_BIN (container), widget); + + if (priv->cell_view && + widget != priv->cell_view) { - g_object_unref (priv->model); - priv->model = NULL; + /* since the cell_view was unparented, it's gone now */ + priv->cell_view = NULL; + + if (!priv->tree_view && priv->separator) + { + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (priv->separator)), + priv->separator); + priv->separator = NULL; + + gtk_widget_queue_resize (GTK_WIDGET (container)); + } + else if (priv->cell_view_frame) + { + gtk_widget_unparent (priv->cell_view_frame); + priv->cell_view_frame = NULL; + priv->box = NULL; + } } - if (priv->active_row) + if (priv->has_entry) { - gtk_tree_row_reference_free (priv->active_row); - priv->active_row = NULL; - } + /* this flag is a hack to tell the entry to fill its allocation. + */ + _gtk_entry_set_is_cell_renderer (GTK_ENTRY (widget), TRUE); - if (priv->cell_view) - gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), NULL); + g_signal_connect (widget, "changed", + G_CALLBACK (gtk_combo_box_entry_contents_changed), + combo_box); + + gtk_entry_set_has_frame (GTK_ENTRY (widget), priv->has_frame); + } } static void -gtk_combo_box_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data) +gtk_combo_box_remove (GtkContainer *container, + GtkWidget *widget) { GtkComboBox *combo_box = GTK_COMBO_BOX (container); GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *child; - + GtkTreePath *path; + gboolean appears_as_list; + + if (priv->has_entry) + { + GtkWidget *child_widget; + + child_widget = gtk_bin_get_child (GTK_BIN (container)); + if (widget && widget == child_widget) + { + g_signal_handlers_disconnect_by_func (widget, + gtk_combo_box_entry_contents_changed, + container); + + _gtk_entry_set_is_cell_renderer (GTK_ENTRY (widget), FALSE); + } + } + + if (widget == priv->cell_view) + priv->cell_view = NULL; + + GTK_CONTAINER_CLASS (gtk_combo_box_parent_class)->remove (container, widget); + + if (gtk_widget_in_destruction (GTK_WIDGET (combo_box))) + return; + + gtk_widget_queue_resize (GTK_WIDGET (container)); + + if (!priv->tree_view) + appears_as_list = FALSE; + else + appears_as_list = TRUE; + + if (appears_as_list) + gtk_combo_box_list_destroy (combo_box); + else if (GTK_IS_MENU (priv->popup_widget)) + { + gtk_combo_box_menu_destroy (combo_box); + gtk_menu_detach (GTK_MENU (priv->popup_widget)); + priv->popup_widget = NULL; + } + + if (!priv->cell_view) + { + priv->cell_view = gtk_cell_view_new_with_context (priv->area, NULL); + gtk_cell_view_set_fit_model (GTK_CELL_VIEW (priv->cell_view), TRUE); + + GTK_CONTAINER_CLASS (gtk_combo_box_parent_class)->add (container, priv->cell_view); + + gtk_widget_show (priv->cell_view); + gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), priv->model); + } + + if (appears_as_list) + gtk_combo_box_list_setup (combo_box); + else + gtk_combo_box_menu_setup (combo_box, TRUE); + + if (gtk_tree_row_reference_valid (priv->active_row)) + { + path = gtk_tree_row_reference_get_path (priv->active_row); + gtk_combo_box_set_active_internal (combo_box, path); + gtk_tree_path_free (path); + } + else + gtk_combo_box_set_active_internal (combo_box, NULL); +} + +static void +gtk_combo_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (container); + GtkComboBoxPrivate *priv = combo_box->priv; + GtkWidget *child; + if (include_internals) { if (priv->button) @@ -2767,1215 +2492,1012 @@ gtk_combo_box_forall (GtkContainer *container, (* callback) (child, callback_data); } -static void -gtk_combo_box_child_show (GtkWidget *widget, - GtkComboBox *combo_box) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - - priv->popup_shown = TRUE; - g_object_notify (G_OBJECT (combo_box), "popup-shown"); -} -static void -gtk_combo_box_child_hide (GtkWidget *widget, - GtkComboBox *combo_box) +/****************************************************** + * GtkComboBoxClass (binding methods) * + ******************************************************/ +static void +gtk_combo_box_real_move_active (GtkComboBox *combo_box, + GtkScrollType scroll) { - GtkComboBoxPrivate *priv = combo_box->priv; + GtkTreeIter iter; + GtkTreeIter new_iter; + gboolean active_iter; + gboolean found; - priv->popup_shown = FALSE; - g_object_notify (G_OBJECT (combo_box), "popup-shown"); -} + if (!combo_box->priv->model) + { + gtk_widget_error_bell (GTK_WIDGET (combo_box)); + return; + } -static gboolean -gtk_combo_box_draw (GtkWidget *widget, - cairo_t *cr) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - GtkComboBoxPrivate *priv = combo_box->priv; + active_iter = gtk_combo_box_get_active_iter (combo_box, &iter); - if (priv->shadow_type != GTK_SHADOW_NONE) + switch (scroll) { - GtkStyleContext *context; - GtkStateFlags state; + case GTK_SCROLL_STEP_BACKWARD: + case GTK_SCROLL_STEP_UP: + case GTK_SCROLL_STEP_LEFT: + if (active_iter) + { + found = tree_prev (combo_box, combo_box->priv->model, + &iter, &new_iter, FALSE); + break; + } + /* else fall through */ - context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); - gtk_style_context_set_state (context, state); + case GTK_SCROLL_PAGE_FORWARD: + case GTK_SCROLL_PAGE_DOWN: + case GTK_SCROLL_PAGE_RIGHT: + case GTK_SCROLL_END: + found = tree_last (combo_box, combo_box->priv->model, &new_iter, FALSE); + break; - gtk_render_background (context, cr, 0, 0, - gtk_widget_get_allocated_width (widget), - gtk_widget_get_allocated_height (widget)); - gtk_render_frame (context, cr, 0, 0, - gtk_widget_get_allocated_width (widget), - gtk_widget_get_allocated_height (widget)); - } + case GTK_SCROLL_STEP_FORWARD: + case GTK_SCROLL_STEP_DOWN: + case GTK_SCROLL_STEP_RIGHT: + if (active_iter) + { + found = tree_next (combo_box, combo_box->priv->model, + &iter, &new_iter, FALSE); + break; + } + /* else fall through */ - gtk_container_propagate_draw (GTK_CONTAINER (widget), - priv->button, cr); + case GTK_SCROLL_PAGE_BACKWARD: + case GTK_SCROLL_PAGE_UP: + case GTK_SCROLL_PAGE_LEFT: + case GTK_SCROLL_START: + found = tree_first (combo_box, combo_box->priv->model, &new_iter, FALSE); + break; - if (priv->tree_view && priv->cell_view_frame) - { - gtk_container_propagate_draw (GTK_CONTAINER (widget), - priv->cell_view_frame, cr); + default: + return; } - gtk_container_propagate_draw (GTK_CONTAINER (widget), - gtk_bin_get_child (GTK_BIN (widget)), - cr); + if (found && active_iter) + { + GtkTreePath *old_path; + GtkTreePath *new_path; - return FALSE; -} + old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter); + new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter); -typedef struct { - GtkComboBox *combo; - GtkTreePath *path; - GtkTreeIter iter; - gboolean found; - gboolean set; - gboolean visible; -} SearchData; + if (gtk_tree_path_compare (old_path, new_path) == 0) + found = FALSE; -static gboolean -path_visible (GtkTreeView *view, - GtkTreePath *path) -{ - GtkRBTree *tree; - GtkRBNode *node; - - /* Note that we rely on the fact that collapsed rows don't have nodes - */ - return _gtk_tree_view_find_node (view, path, &tree, &node); + gtk_tree_path_free (old_path); + gtk_tree_path_free (new_path); + } + + if (found) + { + gtk_combo_box_set_active_iter (combo_box, &new_iter); + } + else + { + gtk_widget_error_bell (GTK_WIDGET (combo_box)); + } } -static gboolean -tree_next_func (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +static void +gtk_combo_box_real_popup (GtkComboBox *combo_box) { - SearchData *search_data = (SearchData *)data; + GdkDevice *device; - if (search_data->found) + device = gtk_get_current_event_device (); + + if (!device) { - if (!tree_column_row_is_sensitive (search_data->combo, iter)) - return FALSE; - - if (search_data->visible && - !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) - return FALSE; + GdkDeviceManager *device_manager; + GdkDisplay *display; + GList *devices; - search_data->set = TRUE; - search_data->iter = *iter; + display = gtk_widget_get_display (GTK_WIDGET (combo_box)); + device_manager = gdk_display_get_device_manager (display); - return TRUE; + /* No device was set, pick the first master device */ + devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); + device = devices->data; + g_list_free (devices); } - - if (gtk_tree_path_compare (path, search_data->path) == 0) - search_data->found = TRUE; - - return FALSE; + + gtk_combo_box_popup_for_device (combo_box, device); } static gboolean -tree_next (GtkComboBox *combo, - GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreeIter *next, - gboolean visible) +gtk_combo_box_real_popdown (GtkComboBox *combo_box) { - SearchData search_data; + if (combo_box->priv->popup_shown) + { + gtk_combo_box_popdown (combo_box); + return TRUE; + } - search_data.combo = combo; - search_data.path = gtk_tree_model_get_path (model, iter); - search_data.visible = visible; - search_data.found = FALSE; - search_data.set = FALSE; - - gtk_tree_model_foreach (model, tree_next_func, &search_data); - - *next = search_data.iter; + return FALSE; +} - gtk_tree_path_free (search_data.path); +/****************************************************** + * GtkTreeModel callbacks * + ******************************************************/ +static void +gtk_combo_box_model_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - return search_data.set; + if (combo_box->priv->tree_view) + gtk_combo_box_list_popup_resize (combo_box); } -static gboolean -tree_prev_func (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +static void +gtk_combo_box_model_row_deleted (GtkTreeModel *model, + GtkTreePath *path, + gpointer user_data) { - SearchData *search_data = (SearchData *)data; + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkComboBoxPrivate *priv = combo_box->priv; - if (gtk_tree_path_compare (path, search_data->path) == 0) + if (!gtk_tree_row_reference_valid (priv->active_row)) { - search_data->found = TRUE; - return TRUE; + if (priv->cell_view) + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL); + g_signal_emit (combo_box, combo_box_signals[CHANGED], 0); } - if (!tree_column_row_is_sensitive (search_data->combo, iter)) - return FALSE; - - if (search_data->visible && - !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) - return FALSE; - - search_data->set = TRUE; - search_data->iter = *iter; - - return FALSE; + if (priv->tree_view) + gtk_combo_box_list_popup_resize (combo_box); } -static gboolean -tree_prev (GtkComboBox *combo, - GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreeIter *prev, - gboolean visible) -{ - SearchData search_data; - - search_data.combo = combo; - search_data.path = gtk_tree_model_get_path (model, iter); - search_data.visible = visible; - search_data.found = FALSE; - search_data.set = FALSE; - - gtk_tree_model_foreach (model, tree_prev_func, &search_data); - - *prev = search_data.iter; - - gtk_tree_path_free (search_data.path); - - return search_data.set; -} -static gboolean -tree_last_func (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +static void +gtk_combo_box_button_toggled (GtkWidget *widget, + gpointer data) { - SearchData *search_data = (SearchData *)data; + GtkComboBox *combo_box = GTK_COMBO_BOX (data); - if (!tree_column_row_is_sensitive (search_data->combo, iter)) - return FALSE; - - /* Note that we rely on the fact that collapsed rows don't have nodes - */ - if (search_data->visible && - !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) - return FALSE; - - search_data->set = TRUE; - search_data->iter = *iter; - - return FALSE; + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + if (!combo_box->priv->popup_in_progress) + gtk_combo_box_popup (combo_box); + } + else + gtk_combo_box_popdown (combo_box); } -static gboolean -tree_last (GtkComboBox *combo, - GtkTreeModel *model, - GtkTreeIter *last, - gboolean visible) +static void +gtk_combo_box_menu_show (GtkWidget *menu, + gpointer user_data) { - SearchData search_data; - - search_data.combo = combo; - search_data.visible = visible; - search_data.set = FALSE; - - gtk_tree_model_foreach (model, tree_last_func, &search_data); - - *last = search_data.iter; - - return search_data.set; -} - + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkComboBoxPrivate *priv = combo_box->priv; -static gboolean -tree_first_func (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - SearchData *search_data = (SearchData *)data; + gtk_combo_box_child_show (menu, user_data); - if (!tree_column_row_is_sensitive (search_data->combo, iter)) - return FALSE; - - if (search_data->visible && - !path_visible (GTK_TREE_VIEW (search_data->combo->priv->tree_view), path)) - return FALSE; - - search_data->set = TRUE; - search_data->iter = *iter; - - return TRUE; + priv->popup_in_progress = TRUE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), + TRUE); + priv->popup_in_progress = FALSE; } -static gboolean -tree_first (GtkComboBox *combo, - GtkTreeModel *model, - GtkTreeIter *first, - gboolean visible) +static void +gtk_combo_box_menu_hide (GtkWidget *menu, + gpointer user_data) { - SearchData search_data; - - search_data.combo = combo; - search_data.visible = visible; - search_data.set = FALSE; + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - gtk_tree_model_foreach (model, tree_first_func, &search_data); - - *first = search_data.iter; + gtk_combo_box_child_hide (menu,user_data); - return search_data.set; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button), + FALSE); } -static gboolean -gtk_combo_box_scroll_event (GtkWidget *widget, - GdkEventScroll *event) +static void +gtk_combo_box_detacher (GtkWidget *widget, + GtkMenu *menu) { GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - gboolean found; - GtkTreeIter iter; - GtkTreeIter new_iter; + GtkComboBoxPrivate *priv = combo_box->priv; - if (!gtk_combo_box_get_active_iter (combo_box, &iter)) - return TRUE; - - if (event->direction == GDK_SCROLL_UP) - found = tree_prev (combo_box, combo_box->priv->model, - &iter, &new_iter, FALSE); - else - found = tree_next (combo_box, combo_box->priv->model, - &iter, &new_iter, FALSE); - - if (found) - gtk_combo_box_set_active_iter (combo_box, &new_iter); + g_return_if_fail (priv->popup_widget == (GtkWidget *) menu); - return TRUE; + g_signal_handlers_disconnect_by_func (menu->toplevel, + gtk_combo_box_menu_show, + combo_box); + g_signal_handlers_disconnect_by_func (menu->toplevel, + gtk_combo_box_menu_hide, + combo_box); + + priv->popup_widget = NULL; } -/* - * menu style - */ - static void -gtk_combo_box_sync_cells (GtkComboBox *combo_box, - GtkCellLayout *cell_layout) +gtk_combo_box_set_popup_widget (GtkComboBox *combo_box, + GtkWidget *popup) { GtkComboBoxPrivate *priv = combo_box->priv; - GSList *k; - for (k = priv->cells; k; k = k->next) + if (GTK_IS_MENU (priv->popup_widget)) { - GSList *j; - ComboCellInfo *info = (ComboCellInfo *)k->data; - - if (info->pack == GTK_PACK_START) - gtk_cell_layout_pack_start (cell_layout, - info->cell, info->expand); - else if (info->pack == GTK_PACK_END) - gtk_cell_layout_pack_end (cell_layout, - info->cell, info->expand); - - gtk_cell_layout_set_cell_data_func (cell_layout, - info->cell, - combo_cell_data_func, info, NULL); + gtk_menu_detach (GTK_MENU (priv->popup_widget)); + priv->popup_widget = NULL; + } + else if (priv->popup_widget) + { + gtk_container_remove (GTK_CONTAINER (priv->scrolled_window), + priv->popup_widget); + g_object_unref (priv->popup_widget); + priv->popup_widget = NULL; + } - for (j = info->attributes; j; j = j->next->next) + if (GTK_IS_MENU (popup)) + { + if (priv->popup_window) { - gtk_cell_layout_add_attribute (cell_layout, - info->cell, - j->data, - GPOINTER_TO_INT (j->next->data)); + gtk_widget_destroy (priv->popup_window); + priv->popup_window = NULL; } - } -} -static void -gtk_combo_box_menu_setup (GtkComboBox *combo_box, - gboolean add_children) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *child; - GtkWidget *menu; + priv->popup_widget = popup; - child = gtk_bin_get_child (GTK_BIN (combo_box)); + /* + * Note that we connect to show/hide on the toplevel, not the + * menu itself, since the menu is not shown/hidden when it is + * popped up while torn-off. + */ + g_signal_connect (GTK_MENU (popup)->toplevel, "show", + G_CALLBACK (gtk_combo_box_menu_show), combo_box); + g_signal_connect (GTK_MENU (popup)->toplevel, "hide", + G_CALLBACK (gtk_combo_box_menu_hide), combo_box); - if (priv->cell_view) + gtk_menu_attach_to_widget (GTK_MENU (popup), + GTK_WIDGET (combo_box), + gtk_combo_box_detacher); + } + else { - priv->button = gtk_toggle_button_new (); - gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), - priv->focus_on_click); + if (!priv->popup_window) + { + GtkWidget *toplevel; + + priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_set_name (priv->popup_window, "gtk-combobox-popup-window"); - g_signal_connect (priv->button, "toggled", - G_CALLBACK (gtk_combo_box_button_toggled), combo_box); - gtk_widget_set_parent (priv->button, - gtk_widget_get_parent (child)); + gtk_window_set_type_hint (GTK_WINDOW (priv->popup_window), + GDK_WINDOW_TYPE_HINT_COMBO); - priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_container_add (GTK_CONTAINER (priv->button), priv->box); + g_signal_connect (GTK_WINDOW (priv->popup_window),"show", + G_CALLBACK (gtk_combo_box_child_show), + combo_box); + g_signal_connect (GTK_WINDOW (priv->popup_window),"hide", + G_CALLBACK (gtk_combo_box_child_hide), + combo_box); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo_box)); + if (GTK_IS_WINDOW (toplevel)) + { + gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (priv->popup_window)); + gtk_window_set_transient_for (GTK_WINDOW (priv->popup_window), + GTK_WINDOW (toplevel)); + } - priv->separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL); - gtk_container_add (GTK_CONTAINER (priv->box), priv->separator); + gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE); + gtk_window_set_screen (GTK_WINDOW (priv->popup_window), + gtk_widget_get_screen (GTK_WIDGET (combo_box))); - priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); - gtk_container_add (GTK_CONTAINER (priv->box), priv->arrow); + priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_SHADOW_IN); - gtk_widget_show_all (priv->button); - } - else - { - priv->button = gtk_toggle_button_new (); - gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), - priv->focus_on_click); + gtk_widget_show (priv->scrolled_window); + + gtk_container_add (GTK_CONTAINER (priv->popup_window), + priv->scrolled_window); + } - g_signal_connect (priv->button, "toggled", - G_CALLBACK (gtk_combo_box_button_toggled), combo_box); - gtk_widget_set_parent (priv->button, - gtk_widget_get_parent (child)); + gtk_container_add (GTK_CONTAINER (priv->scrolled_window), + popup); - priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); - gtk_container_add (GTK_CONTAINER (priv->button), priv->arrow); - gtk_widget_show_all (priv->button); + gtk_widget_show (popup); + g_object_ref (popup); + priv->popup_widget = popup; } - - g_signal_connect (priv->button, "button-press-event", - G_CALLBACK (gtk_combo_box_menu_button_press), - combo_box); - g_signal_connect (priv->button, "state-flags-changed", - G_CALLBACK (gtk_combo_box_button_state_flags_changed), - combo_box); - - /* create our funky menu */ - menu = gtk_menu_new (); - gtk_widget_set_name (menu, "gtk-combobox-popup-menu"); - gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE); - - g_signal_connect (menu, "key-press-event", - G_CALLBACK (gtk_combo_box_menu_key_press), combo_box); - gtk_combo_box_set_popup_widget (combo_box, menu); - - /* add items */ - if (add_children) - gtk_combo_box_menu_fill (combo_box); - - /* the column is needed in tree_column_row_is_sensitive() */ - priv->column = gtk_tree_view_column_new (); - g_object_ref_sink (priv->column); - gtk_combo_box_sync_cells (combo_box, GTK_CELL_LAYOUT (priv->column)); - - gtk_combo_box_update_title (combo_box); - gtk_combo_box_update_sensitivity (combo_box); } static void -gtk_combo_box_menu_fill (GtkComboBox *combo_box) +gtk_combo_box_menu_position_below (GtkMenu *menu, + gint *x, + gint *y, + gint *push_in, + gpointer user_data) { - GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *menu; + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkAllocation child_allocation; + gint sx, sy; + GtkWidget *child; + GtkRequisition req; + GdkScreen *screen; + gint monitor_num; + GdkRectangle monitor; + + /* FIXME: is using the size request here broken? */ + child = gtk_bin_get_child (GTK_BIN (combo_box)); - if (!priv->model) - return; + sx = sy = 0; - menu = priv->popup_widget; + gtk_widget_get_allocation (child, &child_allocation); - if (priv->add_tearoffs) + if (!gtk_widget_get_has_window (child)) { - GtkWidget *tearoff = gtk_tearoff_menu_item_new (); - - gtk_widget_show (tearoff); - - if (priv->wrap_width) - gtk_menu_attach (GTK_MENU (menu), tearoff, 0, priv->wrap_width, 0, 1); - else - gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff); + sx += child_allocation.x; + sy += child_allocation.y; } - gtk_combo_box_menu_fill_level (combo_box, menu, NULL); -} + gdk_window_get_root_coords (gtk_widget_get_window (child), + sx, sy, &sx, &sy); -static GtkWidget * -gtk_cell_view_menu_item_new (GtkComboBox *combo_box, - GtkTreeModel *model, - GtkTreeIter *iter) -{ - GtkWidget *cell_view; - GtkWidget *item; - GtkTreePath *path; - GtkRequisition req; + if (GTK_SHADOW_NONE != combo_box->priv->shadow_type) + sx -= get_widget_border_thickness (GTK_WIDGET (combo_box)); - cell_view = gtk_cell_view_new (); - item = gtk_menu_item_new (); - gtk_container_add (GTK_CONTAINER (item), cell_view); + if (combo_box->priv->popup_fixed_width) + gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL); + else + gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req); - gtk_cell_view_set_model (GTK_CELL_VIEW (cell_view), model); - path = gtk_tree_model_get_path (model, iter); - gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (cell_view), path); - gtk_tree_path_free (path); + if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_LTR) + *x = sx; + else + *x = sx + child_allocation.width - req.width; + *y = sy; - gtk_combo_box_sync_cells (combo_box, GTK_CELL_LAYOUT (cell_view)); - gtk_widget_get_preferred_size (cell_view, &req, NULL); - gtk_widget_show (cell_view); - - return item; -} - -static void -gtk_combo_box_menu_fill_level (GtkComboBox *combo_box, - GtkWidget *menu, - GtkTreeIter *parent) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeModel *model = priv->model; - GtkWidget *item, *submenu, *subitem, *separator; - GtkTreeIter iter; - gboolean is_separator; - gint i, n_children; - GtkWidget *last; - GtkTreePath *path; + screen = gtk_widget_get_screen (GTK_WIDGET (combo_box)); + monitor_num = gdk_screen_get_monitor_at_window (screen, + gtk_widget_get_window (GTK_WIDGET (combo_box))); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); - n_children = gtk_tree_model_iter_n_children (model, parent); + if (*x < monitor.x) + *x = monitor.x; + else if (*x + req.width > monitor.x + monitor.width) + *x = monitor.x + monitor.width - req.width; - last = NULL; - for (i = 0; i < n_children; i++) - { - gtk_tree_model_iter_nth_child (model, &iter, parent, i); + if (monitor.y + monitor.height - *y - child_allocation.height >= req.height) + *y += child_allocation.height; + else if (*y - monitor.y >= req.height) + *y -= req.height; + else if (monitor.y + monitor.height - *y - child_allocation.height > *y - monitor.y) + *y += child_allocation.height; + else + *y -= req.height; - if (priv->row_separator_func) - is_separator = priv->row_separator_func (priv->model, &iter, - priv->row_separator_data); - else - is_separator = FALSE; - - if (is_separator) - { - item = gtk_separator_menu_item_new (); - path = gtk_tree_model_get_path (model, &iter); - g_object_set_data_full (G_OBJECT (item), - I_("gtk-combo-box-item-path"), - gtk_tree_row_reference_new (model, path), - (GDestroyNotify)gtk_tree_row_reference_free); - gtk_tree_path_free (path); - } - else - { - item = gtk_cell_view_menu_item_new (combo_box, model, &iter); - if (gtk_tree_model_iter_has_child (model, &iter)) - { - submenu = gtk_menu_new (); - gtk_menu_set_reserve_toggle_size (GTK_MENU (submenu), FALSE); - gtk_widget_show (submenu); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); - - /* Ugly - since menus can only activate leafs, we have to - * duplicate the item inside the submenu. - */ - subitem = gtk_cell_view_menu_item_new (combo_box, model, &iter); - separator = gtk_separator_menu_item_new (); - gtk_widget_show (subitem); - gtk_widget_show (separator); - g_signal_connect (subitem, "activate", - G_CALLBACK (gtk_combo_box_menu_item_activate), - combo_box); - gtk_menu_shell_append (GTK_MENU_SHELL (submenu), subitem); - gtk_menu_shell_append (GTK_MENU_SHELL (submenu), separator); - - gtk_combo_box_menu_fill_level (combo_box, submenu, &iter); - } - g_signal_connect (item, "activate", - G_CALLBACK (gtk_combo_box_menu_item_activate), - combo_box); - } - - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); - if (priv->wrap_width && menu == priv->popup_widget) - gtk_combo_box_relayout_item (combo_box, item, &iter, last); - gtk_widget_show (item); - - last = item; - } + *push_in = FALSE; } static void -gtk_combo_box_menu_destroy (GtkComboBox *combo_box) +gtk_combo_box_menu_position_over (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) { - GtkComboBoxPrivate *priv = combo_box->priv; - - g_signal_handlers_disconnect_matched (priv->button, - G_SIGNAL_MATCH_DATA, - 0, 0, NULL, - gtk_combo_box_menu_button_press, NULL); - g_signal_handlers_disconnect_matched (priv->button, - G_SIGNAL_MATCH_DATA, - 0, 0, NULL, - gtk_combo_box_button_state_flags_changed, combo_box); + GtkComboBox *combo_box; + GtkWidget *active; + GtkWidget *child; + GtkWidget *widget; + GtkAllocation allocation; + GtkAllocation child_allocation; + GList *children; + gint screen_width; + gint menu_xpos; + gint menu_ypos; + gint menu_width; - /* unparent will remove our latest ref */ - gtk_widget_unparent (priv->button); - - priv->box = NULL; - priv->button = NULL; - priv->arrow = NULL; - priv->separator = NULL; + combo_box = GTK_COMBO_BOX (user_data); + widget = GTK_WIDGET (combo_box); - g_object_unref (priv->column); - priv->column = NULL; + active = gtk_menu_get_active (GTK_MENU (combo_box->priv->popup_widget)); - /* changing the popup window will unref the menu and the children */ -} + gtk_widget_get_allocation (widget, &allocation); -/* - * grid - */ + menu_xpos = allocation.x; + menu_ypos = allocation.y + allocation.height / 2 - 2; -static gboolean -menu_occupied (GtkMenu *menu, - guint left_attach, - guint right_attach, - guint top_attach, - guint bottom_attach) -{ - GList *i; + if (combo_box->priv->popup_fixed_width) + gtk_widget_get_preferred_width (GTK_WIDGET (menu), &menu_width, NULL); + else + gtk_widget_get_preferred_width (GTK_WIDGET (menu), NULL, &menu_width); - for (i = GTK_MENU_SHELL (menu)->priv->children; i; i = i->next) + if (active != NULL) { - guint l, r, b, t; - - gtk_container_child_get (GTK_CONTAINER (menu), - i->data, - "left-attach", &l, - "right-attach", &r, - "bottom-attach", &b, - "top-attach", &t, - NULL); - - /* look if this item intersects with the given coordinates */ - if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b) - return TRUE; + gtk_widget_get_allocation (active, &child_allocation); + menu_ypos -= child_allocation.height / 2; } - return FALSE; -} - -static void -gtk_combo_box_relayout_item (GtkComboBox *combo_box, - GtkWidget *item, - GtkTreeIter *iter, - GtkWidget *last) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - gint current_col = 0, current_row = 0; - gint rows = 1, cols = 1; - GtkWidget *menu = priv->popup_widget; + children = GTK_MENU_SHELL (combo_box->priv->popup_widget)->children; + while (children) + { + child = children->data; - if (!GTK_IS_MENU_SHELL (menu)) - return; + if (active == child) + break; - if (priv->col_column == -1 && - priv->row_column == -1 && - last) - { - gtk_container_child_get (GTK_CONTAINER (menu), - last, - "right-attach", ¤t_col, - "top-attach", ¤t_row, - NULL); - if (current_col + cols > priv->wrap_width) - { - current_col = 0; - current_row++; - } - } - else - { - if (priv->col_column != -1) - gtk_tree_model_get (priv->model, iter, - priv->col_column, &cols, - -1); - if (priv->row_column != -1) - gtk_tree_model_get (priv->model, iter, - priv->row_column, &rows, - -1); - - while (1) + if (gtk_widget_get_visible (child)) { - if (current_col + cols > priv->wrap_width) - { - current_col = 0; - current_row++; - } - - if (!menu_occupied (GTK_MENU (menu), - current_col, current_col + cols, - current_row, current_row + rows)) - break; - - current_col++; + gtk_widget_get_allocation (child, &child_allocation); + + menu_ypos -= child_allocation.height; } + + children = children->next; } - /* set attach props */ - gtk_menu_attach (GTK_MENU (menu), item, - current_col, current_col + cols, - current_row, current_row + rows); -} + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + menu_xpos = menu_xpos + allocation.width - menu_width; -static void -gtk_combo_box_relayout (GtkComboBox *combo_box) -{ - GList *list, *j; - GtkWidget *menu; + gdk_window_get_root_coords (gtk_widget_get_window (widget), + menu_xpos, menu_ypos, + &menu_xpos, &menu_ypos); - menu = combo_box->priv->popup_widget; - - /* do nothing unless we are in menu style and realized */ - if (combo_box->priv->tree_view || !GTK_IS_MENU_SHELL (menu)) - return; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - - for (j = g_list_last (list); j; j = j->prev) - gtk_container_remove (GTK_CONTAINER (menu), j->data); + /* Clamp the position on screen */ + screen_width = gdk_screen_get_width (gtk_widget_get_screen (widget)); - gtk_combo_box_menu_fill (combo_box); + if (menu_xpos < 0) + menu_xpos = 0; + else if ((menu_xpos + menu_width) > screen_width) + menu_xpos -= ((menu_xpos + menu_width) - screen_width); - g_list_free (list); + *x = menu_xpos; + *y = menu_ypos; + + *push_in = TRUE; } -/* callbacks */ -static gboolean -gtk_combo_box_menu_button_press (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) +static void +gtk_combo_box_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gint *push_in, + gpointer user_data) { GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); GtkComboBoxPrivate *priv = combo_box->priv; + GtkWidget *menu_item; - if (GTK_IS_MENU (priv->popup_widget) && - event->type == GDK_BUTTON_PRESS && event->button == 1) + if (priv->wrap_width > 0 || priv->cell_view == NULL) + gtk_combo_box_menu_position_below (menu, x, y, push_in, user_data); + else { - if (priv->focus_on_click && - !gtk_widget_has_focus (priv->button)) - gtk_widget_grab_focus (priv->button); - - gtk_combo_box_menu_popup (combo_box, event->button, event->time); + /* FIXME handle nested menus better */ + menu_item = gtk_menu_get_active (GTK_MENU (priv->popup_widget)); + if (menu_item) + gtk_menu_shell_select_item (GTK_MENU_SHELL (priv->popup_widget), + menu_item); - return TRUE; + gtk_combo_box_menu_position_over (menu, x, y, push_in, user_data); } - return FALSE; + if (!gtk_widget_get_visible (GTK_MENU (priv->popup_widget)->toplevel)) + gtk_window_set_type_hint (GTK_WINDOW (GTK_MENU (priv->popup_widget)->toplevel), + GDK_WINDOW_TYPE_HINT_COMBO); } static void -gtk_combo_box_menu_item_activate (GtkWidget *item, - gpointer user_data) +gtk_combo_box_list_position (GtkComboBox *combo_box, + gint *x, + gint *y, + gint *width, + gint *height) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkWidget *cell_view; - GtkTreePath *path; - GtkTreeIter iter; + GtkComboBoxPrivate *priv = combo_box->priv; + GtkAllocation allocation; + GdkScreen *screen; + gint monitor_num; + GdkRectangle monitor; + GtkRequisition popup_req; + GtkPolicyType hpolicy, vpolicy; + GdkWindow *window; - cell_view = gtk_bin_get_child (GTK_BIN (item)); + /* under windows, the drop down list is as wide as the combo box itself. + see bug #340204 */ + GtkWidget *widget = GTK_WIDGET (combo_box); - g_return_if_fail (GTK_IS_CELL_VIEW (cell_view)); + *x = *y = 0; - path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (cell_view)); + gtk_widget_get_allocation (widget, &allocation); - if (gtk_tree_model_get_iter (combo_box->priv->model, &iter, path)) - { - if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)) == NULL) - gtk_combo_box_set_active_iter (combo_box, &iter); - } + if (!gtk_widget_get_has_window (widget)) + { + *x += allocation.x; + *y += allocation.y; + } - gtk_tree_path_free (path); + window = gtk_widget_get_window (widget); - g_object_set (combo_box, - "editing-canceled", FALSE, - NULL); -} + gdk_window_get_root_coords (gtk_widget_get_window (widget), + *x, *y, x, y); -static void -gtk_combo_box_update_sensitivity (GtkComboBox *combo_box) -{ - GtkTreeIter iter; - gboolean sensitive = TRUE; /* fool code checkers */ + *width = allocation.width; - if (!combo_box->priv->button) - return; + hpolicy = vpolicy = GTK_POLICY_NEVER; + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); - switch (combo_box->priv->button_sensitivity) + if (combo_box->priv->popup_fixed_width) { - case GTK_SENSITIVITY_ON: - sensitive = TRUE; - break; - case GTK_SENSITIVITY_OFF: - sensitive = FALSE; - break; - case GTK_SENSITIVITY_AUTO: - sensitive = combo_box->priv->model && - gtk_tree_model_get_iter_first (combo_box->priv->model, &iter); - break; - default: - g_assert_not_reached (); - break; + gtk_widget_get_preferred_size (priv->scrolled_window, &popup_req, NULL); + + if (popup_req.width > *width) + { + hpolicy = GTK_POLICY_ALWAYS; + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); + } } + else + { + /* This code depends on treeviews properly reporting their natural width */ + gtk_widget_get_preferred_size (priv->scrolled_window, NULL, &popup_req); - gtk_widget_set_sensitive (combo_box->priv->button, sensitive); + if (popup_req.width > *width) + { + hpolicy = GTK_POLICY_NEVER; + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); - /* In list-mode, we also need to update sensitivity of the event box */ - if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view) - && combo_box->priv->cell_view) - gtk_widget_set_sensitive (combo_box->priv->box, sensitive); -} + *width = popup_req.width; + } + } -static void -gtk_combo_box_model_row_inserted (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + *height = popup_req.height; - if (combo_box->priv->tree_view) - gtk_combo_box_list_popup_resize (combo_box); - else - gtk_combo_box_menu_row_inserted (model, path, iter, user_data); + screen = gtk_widget_get_screen (GTK_WIDGET (combo_box)); + monitor_num = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); - gtk_combo_box_update_sensitivity (combo_box); -} + if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_RTL) + *x = *x + allocation.width - *width; -static void -gtk_combo_box_model_row_deleted (GtkTreeModel *model, - GtkTreePath *path, - gpointer user_data) + if (*x < monitor.x) + *x = monitor.x; + else if (*x + *width > monitor.x + monitor.width) + *x = monitor.x + monitor.width - *width; + + if (*y + allocation.height + *height <= monitor.y + monitor.height) + *y += allocation.height; + else if (*y - *height >= monitor.y) + *y -= *height; + else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y) + { + *y += allocation.height; + *height = monitor.y + monitor.height - *y; + } + else + { + *height = *y - monitor.y; + *y = monitor.y; + } + + if (popup_req.height > *height) + { + vpolicy = GTK_POLICY_ALWAYS; + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); + } +} + +static void +gtk_combo_box_menu_popup (GtkComboBox *combo_box, + guint button, + guint32 activate_time) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); GtkComboBoxPrivate *priv = combo_box->priv; - - if (!gtk_tree_row_reference_valid (priv->active_row)) + GtkTreePath *path; + gint active_item; + gint width, min_width, nat_width; + + active_item = -1; + if (gtk_tree_row_reference_valid (priv->active_row)) { - if (priv->cell_view) - gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL); - g_signal_emit (combo_box, combo_box_signals[CHANGED], 0); + path = gtk_tree_row_reference_get_path (priv->active_row); + active_item = gtk_tree_path_get_indices (path)[0]; + gtk_tree_path_free (path); + + if (priv->add_tearoffs) + active_item++; } + + /* FIXME handle nested menus better */ + gtk_menu_set_active (GTK_MENU (priv->popup_widget), active_item); - if (priv->tree_view) - gtk_combo_box_list_popup_resize (combo_box); - else - gtk_combo_box_menu_row_deleted (model, path, user_data); + if (priv->wrap_width == 0) + { + GtkAllocation allocation; - gtk_combo_box_update_sensitivity (combo_box); + gtk_widget_get_allocation (GTK_WIDGET (combo_box), &allocation); + width = allocation.width; + gtk_widget_set_size_request (priv->popup_widget, -1, -1); + gtk_widget_get_preferred_width (priv->popup_widget, &min_width, &nat_width); + + if (combo_box->priv->popup_fixed_width) + width = MAX (width, min_width); + else + width = MAX (width, nat_width); + + gtk_widget_set_size_request (priv->popup_widget, width, -1); + } + + gtk_menu_popup (GTK_MENU (priv->popup_widget), + NULL, NULL, + gtk_combo_box_menu_position, combo_box, + button, activate_time); } -static void -gtk_combo_box_model_rows_reordered (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - gpointer user_data) +static gboolean +popup_grab_on_window (GdkWindow *window, + GdkDevice *keyboard, + GdkDevice *pointer, + guint32 activate_time) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + if (keyboard && + gdk_device_grab (keyboard, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, activate_time) != GDK_GRAB_SUCCESS) + return FALSE; + + if (pointer && + gdk_device_grab (pointer, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, activate_time) != GDK_GRAB_SUCCESS) + { + if (keyboard) + gdk_device_ungrab (keyboard, activate_time); - gtk_tree_row_reference_reordered (G_OBJECT (user_data), path, iter, new_order); + return FALSE; + } - if (!combo_box->priv->tree_view) - gtk_combo_box_menu_rows_reordered (model, path, iter, new_order, user_data); + return TRUE; } - + static void -gtk_combo_box_model_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) +gtk_combo_box_unset_model (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreePath *active_path; - /* FIXME this belongs to GtkCellView */ - if (gtk_tree_row_reference_valid (priv->active_row)) + if (priv->model) { - active_path = gtk_tree_row_reference_get_path (priv->active_row); - if (gtk_tree_path_compare (path, active_path) == 0 && - priv->cell_view) - gtk_widget_queue_resize (GTK_WIDGET (priv->cell_view)); - gtk_tree_path_free (active_path); - } - - if (priv->tree_view) - gtk_combo_box_list_row_changed (model, path, iter, user_data); - else - gtk_combo_box_menu_row_changed (model, path, iter, user_data); -} + g_signal_handler_disconnect (priv->model, + priv->inserted_id); + g_signal_handler_disconnect (priv->model, + priv->deleted_id); -static gboolean -list_popup_resize_idle (gpointer user_data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkComboBoxPrivate *priv = combo_box->priv; - gint x, y, width, height; + g_object_unref (priv->model); + priv->model = NULL; + } - if (priv->tree_view && gtk_widget_get_mapped (priv->popup_window)) + if (priv->active_row) { - gtk_combo_box_list_position (combo_box, &x, &y, &width, &height); - - gtk_widget_set_size_request (priv->popup_window, width, height); - gtk_window_move (GTK_WINDOW (priv->popup_window), x, y); + gtk_tree_row_reference_free (priv->active_row); + priv->active_row = NULL; } - priv->resize_idle_id = 0; - - return FALSE; + if (priv->cell_view) + gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), NULL); } -static void -gtk_combo_box_list_popup_resize (GtkComboBox *combo_box) +static void +gtk_combo_box_child_show (GtkWidget *widget, + GtkComboBox *combo_box) { GtkComboBoxPrivate *priv = combo_box->priv; - if (!priv->resize_idle_id) - priv->resize_idle_id = - gdk_threads_add_idle (list_popup_resize_idle, combo_box); + priv->popup_shown = TRUE; + g_object_notify (G_OBJECT (combo_box), "popup-shown"); } -static void -gtk_combo_box_model_row_expanded (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) +static void +gtk_combo_box_child_hide (GtkWidget *widget, + GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - - gtk_combo_box_list_popup_resize (combo_box); + GtkComboBoxPrivate *priv = combo_box->priv; + + priv->popup_shown = FALSE; + g_object_notify (G_OBJECT (combo_box), "popup-shown"); } +/* + * menu style + */ -static GtkWidget * -find_menu_by_path (GtkWidget *menu, - GtkTreePath *path, - gboolean skip_first) +static gboolean +gtk_combo_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + GtkComboBox *combo) { - GList *i, *list; - GtkWidget *child; - GtkWidget *item; - GtkWidget *submenu; - GtkTreeRowReference *mref; - GtkTreePath *mpath; - gboolean skip; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - skip = skip_first; - item = NULL; - for (i = list; i; i = i->next) - { - child = gtk_bin_get_child (i->data); - if (GTK_IS_SEPARATOR_MENU_ITEM (i->data)) - { - mref = g_object_get_data (G_OBJECT (i->data), "gtk-combo-box-item-path"); - if (!mref) - continue; - else if (!gtk_tree_row_reference_valid (mref)) - mpath = NULL; - else - mpath = gtk_tree_row_reference_get_path (mref); - } - else if (GTK_IS_CELL_VIEW (child)) - { - if (skip) - { - skip = FALSE; - continue; - } + GtkComboBoxPrivate *priv = combo->priv; - mpath = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (child)); - } - else - continue; + if (priv->row_separator_func) + return priv->row_separator_func (model, iter, priv->row_separator_data); - /* this case is necessary, since the row reference of - * the cell view may already be updated after a deletion - */ - if (!mpath) - { - item = i->data; - break; - } - if (gtk_tree_path_compare (mpath, path) == 0) - { - gtk_tree_path_free (mpath); - item = i->data; - break; - } - if (gtk_tree_path_is_ancestor (mpath, path)) - { - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - { - gtk_tree_path_free (mpath); - item = find_menu_by_path (submenu, path, TRUE); - break; - } - } - gtk_tree_path_free (mpath); - } - - g_list_free (list); + return FALSE; +} - return item; +static gboolean +gtk_combo_box_header_func (GtkTreeModel *model, + GtkTreeIter *iter, + GtkComboBox *combo) +{ + /* Every submenu has a selectable header, however we + * can expose a method to make that configurable by + * the user (like row_separator_func is done) */ + return TRUE; } -#if 0 static void -dump_menu_tree (GtkWidget *menu, - gint level) +gtk_combo_box_menu_activate (GtkWidget *menu, + const gchar *path, + GtkComboBox *combo_box) { - GList *i, *list; - GtkWidget *submenu; - GtkTreePath *path; + GtkTreeIter iter; - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - if (GTK_IS_CELL_VIEW (GTK_BIN (i->data)->child)) - { - path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (GTK_BIN (i->data)->child)); - g_print ("%*s%s\n", 2 * level, " ", gtk_tree_path_to_string (path)); - gtk_tree_path_free (path); + if (gtk_tree_model_get_iter_from_string (combo_box->priv->model, &iter, path)) + gtk_combo_box_set_active_iter (combo_box, &iter); - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - dump_menu_tree (submenu, level + 1); - } - } - - g_list_free (list); + g_object_set (combo_box, + "editing-canceled", FALSE, + NULL); } -#endif static void -gtk_combo_box_menu_row_inserted (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) +gtk_combo_box_menu_setup (GtkComboBox *combo_box, + gboolean add_children) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *parent; - GtkWidget *item, *menu, *separator; - GtkTreePath *ppath; - GtkTreeIter piter; - gint depth, pos; - gboolean is_separator; - - if (!priv->popup_widget) - return; + GtkWidget *child; + GtkWidget *menu; - depth = gtk_tree_path_get_depth (path); - pos = gtk_tree_path_get_indices (path)[depth - 1]; - if (depth > 1) - { - ppath = gtk_tree_path_copy (path); - gtk_tree_path_up (ppath); - parent = find_menu_by_path (priv->popup_widget, ppath, FALSE); - gtk_tree_path_free (ppath); + child = gtk_bin_get_child (GTK_BIN (combo_box)); - menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (parent)); - if (!menu) - { - menu = gtk_menu_new (); - gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE); - gtk_widget_show (menu); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent), menu); - - /* Ugly - since menus can only activate leaves, we have to - * duplicate the item inside the submenu. - */ - gtk_tree_model_iter_parent (model, &piter, iter); - item = gtk_cell_view_menu_item_new (combo_box, model, &piter); - separator = gtk_separator_menu_item_new (); - g_signal_connect (item, "activate", - G_CALLBACK (gtk_combo_box_menu_item_activate), - combo_box); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator); - if (cell_view_is_sensitive (GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item))))) - { - gtk_widget_show (item); - gtk_widget_show (separator); - } - } - pos += 2; - } - else + if (priv->cell_view) { - menu = priv->popup_widget; - if (priv->add_tearoffs) - pos += 1; - } - - if (priv->row_separator_func) - is_separator = priv->row_separator_func (model, iter, - priv->row_separator_data); - else - is_separator = FALSE; + priv->button = gtk_toggle_button_new (); + gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), + priv->focus_on_click); - if (is_separator) - { - item = gtk_separator_menu_item_new (); - g_object_set_data_full (G_OBJECT (item), - I_("gtk-combo-box-item-path"), - gtk_tree_row_reference_new (model, path), - (GDestroyNotify)gtk_tree_row_reference_free); + g_signal_connect (priv->button, "toggled", + G_CALLBACK (gtk_combo_box_button_toggled), combo_box); + gtk_widget_set_parent (priv->button, + gtk_widget_get_parent (child)); + + priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (priv->button), priv->box); + + priv->separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (priv->box), priv->separator); + + priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (priv->box), priv->arrow); + + gtk_widget_show_all (priv->button); } else { - item = gtk_cell_view_menu_item_new (combo_box, model, iter); - - g_signal_connect (item, "activate", - G_CALLBACK (gtk_combo_box_menu_item_activate), - combo_box); - } + priv->button = gtk_toggle_button_new (); + gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), + priv->focus_on_click); - gtk_widget_show (item); - gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, pos); -} + g_signal_connect (priv->button, "toggled", + G_CALLBACK (gtk_combo_box_button_toggled), combo_box); + gtk_widget_set_parent (priv->button, + gtk_widget_get_parent (child)); -static void -gtk_combo_box_menu_row_deleted (GtkTreeModel *model, - GtkTreePath *path, - gpointer user_data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *menu; - GtkWidget *item; + priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (priv->button), priv->arrow); + gtk_widget_show_all (priv->button); + } - if (!priv->popup_widget) - return; + g_signal_connect (priv->button, "button-press-event", + G_CALLBACK (gtk_combo_box_menu_button_press), + combo_box); + g_signal_connect (priv->button, "state-changed", + G_CALLBACK (gtk_combo_box_button_state_changed), + combo_box); - item = find_menu_by_path (priv->popup_widget, path, FALSE); - menu = gtk_widget_get_parent (item); - gtk_container_remove (GTK_CONTAINER (menu), item); + /* create our funky menu */ + menu = gtk_tree_menu_new_with_area (priv->area); + gtk_tree_menu_set_model (GTK_TREE_MENU (menu), priv->model); - if (gtk_tree_path_get_depth (path) > 1) - { - GtkTreePath *parent_path; - GtkTreeIter iter; - GtkWidget *parent; + gtk_tree_menu_set_wrap_width (GTK_TREE_MENU (menu), priv->wrap_width); + gtk_tree_menu_set_row_span_column (GTK_TREE_MENU (menu), priv->row_column); + gtk_tree_menu_set_column_span_column (GTK_TREE_MENU (menu), priv->col_column); + gtk_tree_menu_set_tearoff (GTK_TREE_MENU (menu), + combo_box->priv->add_tearoffs); - parent_path = gtk_tree_path_copy (path); - gtk_tree_path_up (parent_path); - gtk_tree_model_get_iter (model, &iter, parent_path); + g_signal_connect (menu, "menu-activate", + G_CALLBACK (gtk_combo_box_menu_activate), combo_box); - if (!gtk_tree_model_iter_has_child (model, &iter)) - { - parent = find_menu_by_path (priv->popup_widget, - parent_path, FALSE); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent), NULL); - } - } -} + /* Chain our row_separator_func through */ + gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (menu), + (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func, + combo_box, NULL); -static void -gtk_combo_box_menu_rows_reordered (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - gpointer user_data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + gtk_tree_menu_set_header_func (GTK_TREE_MENU (menu), + (GtkTreeMenuHeaderFunc)gtk_combo_box_header_func, + combo_box, NULL); + + gtk_widget_set_name (menu, "gtk-combobox-popup-menu"); + + g_signal_connect (menu, "key-press-event", + G_CALLBACK (gtk_combo_box_menu_key_press), combo_box); + gtk_combo_box_set_popup_widget (combo_box, menu); - gtk_combo_box_relayout (combo_box); + gtk_combo_box_update_title (combo_box); } - + static void -gtk_combo_box_menu_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) +gtk_combo_box_menu_destroy (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); GtkComboBoxPrivate *priv = combo_box->priv; - GtkWidget *item; - gboolean is_separator; - if (!priv->popup_widget) - return; - - item = find_menu_by_path (priv->popup_widget, path, FALSE); + g_signal_handlers_disconnect_matched (priv->button, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, + gtk_combo_box_menu_button_press, NULL); + g_signal_handlers_disconnect_matched (priv->button, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, + gtk_combo_box_button_state_changed, combo_box); + g_signal_handlers_disconnect_matched (priv->popup_widget, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, + gtk_combo_box_menu_activate, combo_box); - if (priv->row_separator_func) - is_separator = priv->row_separator_func (model, iter, - priv->row_separator_data); - else - is_separator = FALSE; + /* unparent will remove our latest ref */ + gtk_widget_unparent (priv->button); - if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item)) - { - gtk_combo_box_menu_row_deleted (model, path, combo_box); - gtk_combo_box_menu_row_inserted (model, path, iter, combo_box); - } + priv->box = NULL; + priv->button = NULL; + priv->arrow = NULL; + priv->separator = NULL; - if (priv->wrap_width && - gtk_widget_get_parent (item) == priv->popup_widget) - { - GtkWidget *pitem = NULL; - GtkTreePath *prev; + priv->column = NULL; - prev = gtk_tree_path_copy (path); + /* changing the popup window will unref the menu and the children */ +} - if (gtk_tree_path_prev (prev)) - pitem = find_menu_by_path (priv->popup_widget, prev, FALSE); +/* callbacks */ +static gboolean +gtk_combo_box_menu_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkComboBoxPrivate *priv = combo_box->priv; - gtk_tree_path_free (prev); + if (GTK_IS_MENU (priv->popup_widget) && + event->type == GDK_BUTTON_PRESS && event->button == 1) + { + if (priv->focus_on_click && + !gtk_widget_has_focus (priv->button)) + gtk_widget_grab_focus (priv->button); - /* unattach item so gtk_combo_box_relayout_item() won't spuriously - move it */ - gtk_container_child_set (GTK_CONTAINER (priv->popup_widget), - item, - "left-attach", -1, - "right-attach", -1, - "top-attach", -1, - "bottom-attach", -1, - NULL); + gtk_combo_box_menu_popup (combo_box, event->button, event->time); - gtk_combo_box_relayout_item (combo_box, item, iter, pitem); + return TRUE; } - gtk_combo_box_update_requested_width (combo_box, path); + return FALSE; } -/* - * list style - */ - static void -gtk_combo_box_list_setup (GtkComboBox *combo_box) +gtk_combo_box_update_sensitivity (GtkComboBox *combo_box) { - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeSelection *sel; - GtkWidget *child; - GtkWidget *widget = GTK_WIDGET (combo_box); + GtkTreeIter iter; + gboolean sensitive = TRUE; /* fool code checkers */ - priv->button = gtk_toggle_button_new (); - child = gtk_bin_get_child (GTK_BIN (combo_box)); - gtk_widget_set_parent (priv->button, - gtk_widget_get_parent (child)); - g_signal_connect (priv->button, "button-press-event", - G_CALLBACK (gtk_combo_box_list_button_pressed), combo_box); - g_signal_connect (priv->button, "toggled", - G_CALLBACK (gtk_combo_box_button_toggled), combo_box); + if (!combo_box->priv->button) + return; - priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); - gtk_container_add (GTK_CONTAINER (priv->button), priv->arrow); - priv->separator = NULL; - gtk_widget_show_all (priv->button); + switch (combo_box->priv->button_sensitivity) + { + case GTK_SENSITIVITY_ON: + sensitive = TRUE; + break; + case GTK_SENSITIVITY_OFF: + sensitive = FALSE; + break; + case GTK_SENSITIVITY_AUTO: + sensitive = combo_box->priv->model && + gtk_tree_model_get_iter_first (combo_box->priv->model, &iter); + break; + default: + g_assert_not_reached (); + break; + } + + gtk_widget_set_sensitive (combo_box->priv->button, sensitive); + + /* In list-mode, we also need to update sensitivity of the event box */ + if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view) + && combo_box->priv->cell_view) + gtk_widget_set_sensitive (combo_box->priv->box, sensitive); +} + +static gboolean +list_popup_resize_idle (gpointer user_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + GtkComboBoxPrivate *priv = combo_box->priv; + gint x, y, width, height; + + if (priv->tree_view && gtk_widget_get_mapped (priv->popup_window)) + { + gtk_combo_box_list_position (combo_box, &x, &y, &width, &height); + + gtk_widget_set_size_request (priv->popup_window, width, height); + gtk_window_move (GTK_WINDOW (priv->popup_window), x, y); + } + + priv->resize_idle_id = 0; + + return FALSE; +} + +static void +gtk_combo_box_list_popup_resize (GtkComboBox *combo_box) +{ + GtkComboBoxPrivate *priv = combo_box->priv; + + if (!priv->resize_idle_id) + priv->resize_idle_id = + gdk_threads_add_idle (list_popup_resize_idle, combo_box); +} + +static void +gtk_combo_box_model_row_expanded (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); + + gtk_combo_box_list_popup_resize (combo_box); +} + +/* + * list style + */ + +static void +gtk_combo_box_list_setup (GtkComboBox *combo_box) +{ + GtkComboBoxPrivate *priv = combo_box->priv; + GtkTreeSelection *sel; + GtkWidget *child; + GtkWidget *widget = GTK_WIDGET (combo_box); + + priv->button = gtk_toggle_button_new (); + child = gtk_bin_get_child (GTK_BIN (combo_box)); + gtk_widget_set_parent (priv->button, + gtk_widget_get_parent (child)); + g_signal_connect (priv->button, "button-press-event", + G_CALLBACK (gtk_combo_box_list_button_pressed), combo_box); + g_signal_connect (priv->button, "toggled", + G_CALLBACK (gtk_combo_box_button_toggled), combo_box); + + priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (priv->button), priv->arrow); + priv->separator = NULL; + gtk_widget_show_all (priv->button); if (priv->cell_view) { @@ -4030,21 +3552,16 @@ gtk_combo_box_list_setup (GtkComboBox *combo_box) FALSE); gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (priv->tree_view), TRUE); - if (priv->row_separator_func) - gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (priv->tree_view), - priv->row_separator_func, - priv->row_separator_data, - NULL); + + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (priv->tree_view), + (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func, + combo_box, NULL); if (priv->model) gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), priv->model); priv->column = gtk_tree_view_column_new (); gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), priv->column); - /* sync up */ - gtk_combo_box_sync_cells (combo_box, - GTK_CELL_LAYOUT (priv->column)); - if (gtk_tree_row_reference_valid (priv->active_row)) { GtkTreePath *path; @@ -4461,472 +3978,221 @@ gtk_combo_box_list_select_func (GtkTreeSelection *selection, return sensitive; } -static void -gtk_combo_box_list_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (data); - - gtk_combo_box_update_requested_width (combo_box, path); -} /* - * GtkCellLayout implementation + * GtkCellEditable implementation */ static void -pack_start_recurse (GtkWidget *menu, - GtkCellRenderer *cell, - gboolean expand) +gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface) { - GList *i, *list; - GtkWidget *child; - GtkWidget *submenu; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (child), - cell, expand); - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - pack_start_recurse (submenu, cell, expand); - } - - g_list_free (list); + iface->start_editing = gtk_combo_box_start_editing; } -static void -gtk_combo_box_cell_layout_pack_start (GtkCellLayout *layout, - GtkCellRenderer *cell, - gboolean expand) +static gboolean +gtk_cell_editable_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer data) { - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - ComboCellInfo *info; - GtkComboBoxPrivate *priv; - - priv = combo_box->priv; - - g_object_ref_sink (cell); - - info = g_slice_new0 (ComboCellInfo); - info->cell = cell; - info->expand = expand; - info->pack = GTK_PACK_START; - - priv->cells = g_slist_append (priv->cells, info); + GtkComboBox *combo_box = GTK_COMBO_BOX (data); - if (priv->cell_view) + if (event->keyval == GDK_KEY_Escape) { - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->cell_view), - cell, expand); - + g_object_set (combo_box, + "editing-canceled", TRUE, + NULL); + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); + + return TRUE; } - - if (priv->column) - gtk_tree_view_column_pack_start (priv->column, cell, expand); - - if (GTK_IS_MENU (priv->popup_widget)) - pack_start_recurse (priv->popup_widget, cell, expand); -} - -static void -pack_end_recurse (GtkWidget *menu, - GtkCellRenderer *cell, - gboolean expand) -{ - GList *i, *list; - GtkWidget *child; - GtkWidget *submenu; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) + else if (event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_ISO_Enter || + event->keyval == GDK_KEY_KP_Enter) { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (child), - cell, expand); - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - pack_end_recurse (submenu, cell, expand); + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); + + return TRUE; } - g_list_free (list); + return FALSE; } -static void -gtk_combo_box_cell_layout_pack_end (GtkCellLayout *layout, - GtkCellRenderer *cell, - gboolean expand) +static gboolean +popdown_idle (gpointer data) { - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - ComboCellInfo *info; - GtkComboBoxPrivate *priv; - - priv = combo_box->priv; - - g_object_ref_sink (cell); - - info = g_slice_new0 (ComboCellInfo); - info->cell = cell; - info->expand = expand; - info->pack = GTK_PACK_END; + GtkComboBox *combo_box; - priv->cells = g_slist_append (priv->cells, info); + combo_box = GTK_COMBO_BOX (data); + + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); - if (priv->cell_view) - gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->cell_view), - cell, expand); + g_object_unref (combo_box); - if (priv->column) - gtk_tree_view_column_pack_end (priv->column, cell, expand); + return FALSE; +} - if (GTK_IS_MENU (priv->popup_widget)) - pack_end_recurse (priv->popup_widget, cell, expand); +static void +popdown_handler (GtkWidget *widget, + gpointer data) +{ + gdk_threads_add_idle (popdown_idle, g_object_ref (data)); } -static GList * -gtk_combo_box_cell_layout_get_cells (GtkCellLayout *layout) +static gboolean +popup_idle (gpointer data) { - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - GSList *list; - GList *retval = NULL; + GtkComboBox *combo_box; - for (list = combo_box->priv->cells; list; list = list->next) - { - ComboCellInfo *info = (ComboCellInfo *)list->data; + combo_box = GTK_COMBO_BOX (data); - retval = g_list_prepend (retval, info->cell); - } + if (GTK_IS_MENU (combo_box->priv->popup_widget) && + combo_box->priv->cell_view) + g_signal_connect_object (combo_box->priv->popup_widget, + "unmap", G_CALLBACK (popdown_handler), + combo_box, 0); + + /* we unset this if a menu item is activated */ + g_object_set (combo_box, + "editing-canceled", TRUE, + NULL); + gtk_combo_box_popup (combo_box); + + combo_box->priv->popup_idle_id = 0; + combo_box->priv->activate_button = 0; + combo_box->priv->activate_time = 0; - return g_list_reverse (retval); + return FALSE; } static void -clear_recurse (GtkWidget *menu) +gtk_combo_box_start_editing (GtkCellEditable *cell_editable, + GdkEvent *event) { - GList *i, *list; + GtkComboBox *combo_box = GTK_COMBO_BOX (cell_editable); GtkWidget *child; - GtkWidget *submenu; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) + + combo_box->priv->is_cell_renderer = TRUE; + + if (combo_box->priv->cell_view) { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_clear (GTK_CELL_LAYOUT (child)); + g_signal_connect_object (combo_box->priv->button, "key-press-event", + G_CALLBACK (gtk_cell_editable_key_press), + cell_editable, 0); - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - clear_recurse (submenu); + gtk_widget_grab_focus (combo_box->priv->button); } + else + { + child = gtk_bin_get_child (GTK_BIN (combo_box)); - g_list_free (list); -} - -static void -gtk_combo_box_cell_layout_clear (GtkCellLayout *layout) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - GtkComboBoxPrivate *priv = combo_box->priv; - GSList *i; - - if (priv->cell_view) - gtk_cell_layout_clear (GTK_CELL_LAYOUT (priv->cell_view)); + g_signal_connect_object (child, "key-press-event", + G_CALLBACK (gtk_cell_editable_key_press), + cell_editable, 0); - if (priv->column) - gtk_tree_view_column_clear (priv->column); + gtk_widget_grab_focus (child); + gtk_widget_set_can_focus (combo_box->priv->button, FALSE); + } - for (i = priv->cells; i; i = i->next) + /* we do the immediate popup only for the optionmenu-like + * appearance + */ + if (combo_box->priv->is_cell_renderer && + combo_box->priv->cell_view && !combo_box->priv->tree_view) { - ComboCellInfo *info = (ComboCellInfo *)i->data; + if (event && event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *event_button = (GdkEventButton *)event; - gtk_combo_box_cell_layout_clear_attributes (layout, info->cell); - g_object_unref (info->cell); - g_slice_free (ComboCellInfo, info); - i->data = NULL; - } - g_slist_free (priv->cells); - priv->cells = NULL; + combo_box->priv->activate_button = event_button->button; + combo_box->priv->activate_time = event_button->time; + } - if (GTK_IS_MENU (priv->popup_widget)) - clear_recurse (priv->popup_widget); + combo_box->priv->popup_idle_id = + gdk_threads_add_idle (popup_idle, combo_box); + } } -static void -add_attribute_recurse (GtkWidget *menu, - GtkCellRenderer *cell, - const gchar *attribute, - gint column) -{ - GList *i, *list; - GtkWidget *child; - GtkWidget *submenu; - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (child), - cell, attribute, column); - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - add_attribute_recurse (submenu, cell, attribute, column); - } +/* + * GtkCellLayout implementation + */ - g_list_free (list); -} - static void -gtk_combo_box_cell_layout_add_attribute (GtkCellLayout *layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column) +gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->get_area = gtk_combo_box_cell_layout_get_area; +} + +static GtkCellArea * +gtk_combo_box_cell_layout_get_area (GtkCellLayout *layout) { GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - ComboCellInfo *info; + GtkComboBoxPrivate *priv; - info = gtk_combo_box_get_cell_info (combo_box, cell); - g_return_if_fail (info != NULL); + priv = combo_box->priv; - info->attributes = g_slist_prepend (info->attributes, - GINT_TO_POINTER (column)); - info->attributes = g_slist_prepend (info->attributes, - g_strdup (attribute)); + return priv->area; +} - if (combo_box->priv->cell_view) - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box->priv->cell_view), - cell, attribute, column); - - if (combo_box->priv->column) - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box->priv->column), - cell, attribute, column); - - if (GTK_IS_MENU (combo_box->priv->popup_widget)) - add_attribute_recurse (combo_box->priv->popup_widget, cell, attribute, column); - gtk_widget_queue_resize (GTK_WIDGET (combo_box)); -} - -static void -combo_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data) -{ - ComboCellInfo *info = (ComboCellInfo *)data; - GtkWidget *parent = NULL; - - if (!info->func) - return; - - info->func (cell_layout, cell, tree_model, iter, info->func_data); - - if (GTK_IS_WIDGET (cell_layout)) - parent = gtk_widget_get_parent (GTK_WIDGET (cell_layout)); - - if (GTK_IS_MENU_ITEM (parent) && - gtk_menu_item_get_submenu (GTK_MENU_ITEM (parent))) - g_object_set (cell, "sensitive", TRUE, NULL); -} - - -static void -set_cell_data_func_recurse (GtkWidget *menu, - GtkCellRenderer *cell, - ComboCellInfo *info) -{ - GList *i, *list; - GtkWidget *submenu; - GtkWidget *cell_view; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - cell_view = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (cell_view)) - { - /* Override sensitivity for inner nodes; we don't - * want menuitems with submenus to appear insensitive - */ - gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (cell_view), - cell, - combo_cell_data_func, - info, NULL); - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - set_cell_data_func_recurse (submenu, cell, info); - } - } - - g_list_free (list); -} +/* + * GtkBuildable implementation + */ static void -gtk_combo_box_cell_layout_set_cell_data_func (GtkCellLayout *layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy) +gtk_combo_box_buildable_init (GtkBuildableIface *iface) { - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - GtkComboBoxPrivate *priv = combo_box->priv; - ComboCellInfo *info; - - info = gtk_combo_box_get_cell_info (combo_box, cell); - g_return_if_fail (info != NULL); - - if (info->destroy) - { - GDestroyNotify d = info->destroy; - - info->destroy = NULL; - d (info->func_data); - } - - info->func = func; - info->func_data = func_data; - info->destroy = destroy; - - if (priv->cell_view) - gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->cell_view), cell, func, func_data, NULL); - - if (priv->column) - gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->column), cell, func, func_data, NULL); - - if (GTK_IS_MENU (priv->popup_widget)) - set_cell_data_func_recurse (priv->popup_widget, cell, info); - - gtk_widget_queue_resize (GTK_WIDGET (combo_box)); + parent_buildable_iface = g_type_interface_peek_parent (iface); + iface->add_child = _gtk_cell_layout_buildable_add_child; + iface->custom_tag_start = gtk_combo_box_buildable_custom_tag_start; + iface->custom_tag_end = gtk_combo_box_buildable_custom_tag_end; + iface->get_internal_child = gtk_combo_box_buildable_get_internal_child; } -static void -clear_attributes_recurse (GtkWidget *menu, - GtkCellRenderer *cell) +static gboolean +gtk_combo_box_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *data) { - GList *i, *list; - GtkWidget *submenu; - GtkWidget *child; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (child), cell); - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - clear_attributes_recurse (submenu, cell); - } + if (parent_buildable_iface->custom_tag_start (buildable, builder, child, + tagname, parser, data)) + return TRUE; - g_list_free (list); + return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child, + tagname, parser, data); } static void -gtk_combo_box_cell_layout_clear_attributes (GtkCellLayout *layout, - GtkCellRenderer *cell) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - GtkComboBoxPrivate *priv; - ComboCellInfo *info; - GSList *list; - - priv = combo_box->priv; - - info = gtk_combo_box_get_cell_info (combo_box, cell); - g_return_if_fail (info != NULL); - - list = info->attributes; - while (list && list->next) - { - g_free (list->data); - list = list->next->next; - } - g_slist_free (info->attributes); - info->attributes = NULL; - - if (priv->cell_view) - gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (priv->cell_view), cell); - - if (priv->column) - gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (priv->column), cell); - - if (GTK_IS_MENU (priv->popup_widget)) - clear_attributes_recurse (priv->popup_widget, cell); - - gtk_widget_queue_resize (GTK_WIDGET (combo_box)); -} - -static void -reorder_recurse (GtkWidget *menu, - GtkCellRenderer *cell, - gint position) +gtk_combo_box_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) { - GList *i, *list; - GtkWidget *child; - GtkWidget *submenu; - - list = gtk_container_get_children (GTK_CONTAINER (menu)); - for (i = list; i; i = i->next) - { - child = gtk_bin_get_child (GTK_BIN (i->data)); - if (GTK_IS_CELL_LAYOUT (child)) - gtk_cell_layout_reorder (GTK_CELL_LAYOUT (child), - cell, position); - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (i->data)); - if (submenu != NULL) - reorder_recurse (submenu, cell, position); - } - - g_list_free (list); + if (_gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, + data)) + return; + else + parent_buildable_iface->custom_tag_end (buildable, builder, child, tagname, + data); } -static void -gtk_combo_box_cell_layout_reorder (GtkCellLayout *layout, - GtkCellRenderer *cell, - gint position) +static GObject * +gtk_combo_box_buildable_get_internal_child (GtkBuildable *buildable, + GtkBuilder *builder, + const gchar *childname) { - GtkComboBox *combo_box = GTK_COMBO_BOX (layout); - GtkComboBoxPrivate *priv; - ComboCellInfo *info; - GSList *link; - - priv = combo_box->priv; - - info = gtk_combo_box_get_cell_info (combo_box, cell); - - g_return_if_fail (info != NULL); - g_return_if_fail (position >= 0); - - link = g_slist_find (priv->cells, info); - - g_return_if_fail (link != NULL); - - priv->cells = g_slist_delete_link (priv->cells, link); - priv->cells = g_slist_insert (priv->cells, info, position); - - if (priv->cell_view) - gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->cell_view), - cell, position); - - if (priv->column) - gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->column), - cell, position); + GtkComboBox *combo_box = GTK_COMBO_BOX (buildable); - if (GTK_IS_MENU (priv->popup_widget)) - reorder_recurse (priv->popup_widget, cell, position); + if (combo_box->priv->has_entry && strcmp (childname, "entry") == 0) + return G_OBJECT (gtk_bin_get_child (GTK_BIN (buildable))); - gtk_widget_queue_draw (GTK_WIDGET (combo_box)); + return parent_buildable_iface->get_internal_child (buildable, builder, childname); } /* @@ -5048,7 +4314,9 @@ gtk_combo_box_set_wrap_width (GtkComboBox *combo_box, priv->wrap_width = width; gtk_combo_box_check_appearance (combo_box); - gtk_combo_box_relayout (combo_box); + + if (GTK_IS_TREE_MENU (priv->popup_widget)) + gtk_tree_menu_set_wrap_width (GTK_TREE_MENU (priv->popup_widget), priv->wrap_width); g_object_notify (G_OBJECT (combo_box), "wrap-width"); } @@ -5100,8 +4368,9 @@ gtk_combo_box_set_row_span_column (GtkComboBox *combo_box, if (row_span != priv->row_column) { priv->row_column = row_span; - - gtk_combo_box_relayout (combo_box); + + if (GTK_IS_TREE_MENU (priv->popup_widget)) + gtk_tree_menu_set_row_span_column (GTK_TREE_MENU (priv->popup_widget), priv->row_column); g_object_notify (G_OBJECT (combo_box), "row-span-column"); } @@ -5153,9 +4422,10 @@ gtk_combo_box_set_column_span_column (GtkComboBox *combo_box, if (column_span != priv->col_column) { priv->col_column = column_span; - - gtk_combo_box_relayout (combo_box); + if (GTK_IS_TREE_MENU (priv->popup_widget)) + gtk_tree_menu_set_column_span_column (GTK_TREE_MENU (priv->popup_widget), priv->col_column); + g_object_notify (G_OBJECT (combo_box), "column-span-column"); } } @@ -5406,15 +4676,7 @@ gtk_combo_box_set_model (GtkComboBox *combo_box, g_signal_connect (combo_box->priv->model, "row-deleted", G_CALLBACK (gtk_combo_box_model_row_deleted), combo_box); - combo_box->priv->reordered_id = - g_signal_connect (combo_box->priv->model, "rows-reordered", - G_CALLBACK (gtk_combo_box_model_rows_reordered), - combo_box); - combo_box->priv->changed_id = - g_signal_connect (combo_box->priv->model, "row-changed", - G_CALLBACK (gtk_combo_box_model_row_changed), - combo_box); - + if (combo_box->priv->tree_view) { /* list mode */ @@ -5422,12 +4684,12 @@ gtk_combo_box_set_model (GtkComboBox *combo_box, combo_box->priv->model); gtk_combo_box_list_popup_resize (combo_box); } - else + + if (GTK_IS_TREE_MENU (combo_box->priv->popup_widget)) { /* menu mode */ - if (combo_box->priv->popup_widget) - gtk_combo_box_menu_fill (combo_box); - + gtk_tree_menu_set_model (GTK_TREE_MENU (combo_box->priv->popup_widget), + combo_box->priv->model); } if (combo_box->priv->cell_view) @@ -5442,8 +4704,6 @@ gtk_combo_box_set_model (GtkComboBox *combo_box, } out: - gtk_combo_box_update_sensitivity (combo_box); - g_object_notify (G_OBJECT (combo_box), "model"); } @@ -5467,1238 +4727,644 @@ gtk_combo_box_get_model (GtkComboBox *combo_box) } static void -gtk_combo_box_real_move_active (GtkComboBox *combo_box, - GtkScrollType scroll) +gtk_combo_box_entry_contents_changed (GtkEntry *entry, + gpointer user_data) { - GtkTreeIter iter; - GtkTreeIter new_iter; - gboolean active_iter; - gboolean found; + GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - if (!combo_box->priv->model) - { - gtk_widget_error_bell (GTK_WIDGET (combo_box)); - return; - } + /* + * Fixes regression reported in bug #574059. The old functionality relied on + * bug #572478. As a bugfix, we now emit the "changed" signal ourselves + * when the selection was already set to -1. + */ + if (gtk_combo_box_get_active(combo_box) == -1) + g_signal_emit_by_name (combo_box, "changed"); + else + gtk_combo_box_set_active (combo_box, -1); +} - active_iter = gtk_combo_box_get_active_iter (combo_box, &iter); +static void +gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, + gpointer user_data) +{ + GtkComboBoxPrivate *priv = combo_box->priv; + GtkTreeModel *model; + GtkTreeIter iter; - switch (scroll) + if (gtk_combo_box_get_active_iter (combo_box, &iter)) { - case GTK_SCROLL_STEP_BACKWARD: - case GTK_SCROLL_STEP_UP: - case GTK_SCROLL_STEP_LEFT: - if (active_iter) - { - found = tree_prev (combo_box, combo_box->priv->model, - &iter, &new_iter, FALSE); - break; - } - /* else fall through */ - - case GTK_SCROLL_PAGE_FORWARD: - case GTK_SCROLL_PAGE_DOWN: - case GTK_SCROLL_PAGE_RIGHT: - case GTK_SCROLL_END: - found = tree_last (combo_box, combo_box->priv->model, &new_iter, FALSE); - break; - - case GTK_SCROLL_STEP_FORWARD: - case GTK_SCROLL_STEP_DOWN: - case GTK_SCROLL_STEP_RIGHT: - if (active_iter) - { - found = tree_next (combo_box, combo_box->priv->model, - &iter, &new_iter, FALSE); - break; - } - /* else fall through */ - - case GTK_SCROLL_PAGE_BACKWARD: - case GTK_SCROLL_PAGE_UP: - case GTK_SCROLL_PAGE_LEFT: - case GTK_SCROLL_START: - found = tree_first (combo_box, combo_box->priv->model, &new_iter, FALSE); - break; - - default: - return; - } + GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo_box))); - if (found && active_iter) - { - GtkTreePath *old_path; - GtkTreePath *new_path; + if (entry) + { + GValue value = {0,}; - old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter); - new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter); + g_signal_handlers_block_by_func (entry, + gtk_combo_box_entry_contents_changed, + combo_box); - if (gtk_tree_path_compare (old_path, new_path) == 0) - found = FALSE; + model = gtk_combo_box_get_model (combo_box); - gtk_tree_path_free (old_path); - gtk_tree_path_free (new_path); - } + gtk_tree_model_get_value (model, &iter, + priv->text_column, &value); + g_object_set_property (G_OBJECT (entry), "text", &value); + g_value_unset (&value); - if (found) - { - gtk_combo_box_set_active_iter (combo_box, &new_iter); - } - else - { - gtk_widget_error_bell (GTK_WIDGET (combo_box)); + g_signal_handlers_unblock_by_func (entry, + gtk_combo_box_entry_contents_changed, + combo_box); + } } } -static gboolean -gtk_combo_box_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling) +/** + * gtk_combo_box_get_add_tearoffs: + * @combo_box: a #GtkComboBox + * + * Gets the current value of the :add-tearoffs property. + * + * Return value: the current value of the :add-tearoffs property. + */ +gboolean +gtk_combo_box_get_add_tearoffs (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - if (combo_box->priv->has_entry) - { - GtkWidget* child; - - child = gtk_bin_get_child (GTK_BIN (combo_box)); - if (child) - gtk_widget_grab_focus (child); - } - else - gtk_widget_grab_focus (combo_box->priv->button); - - return TRUE; + return combo_box->priv->add_tearoffs; } -static void -gtk_combo_box_grab_focus (GtkWidget *widget) +/** + * gtk_combo_box_set_add_tearoffs: + * @combo_box: a #GtkComboBox + * @add_tearoffs: %TRUE to add tearoff menu items + * + * Sets whether the popup menu should have a tearoff + * menu item. + * + * Since: 2.6 + */ +void +gtk_combo_box_set_add_tearoffs (GtkComboBox *combo_box, + gboolean add_tearoffs) { - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - - if (combo_box->priv->has_entry) - { - GtkWidget *child; - - child = gtk_bin_get_child (GTK_BIN (combo_box)); - if (child) - gtk_widget_grab_focus (child); - } - else - gtk_widget_grab_focus (combo_box->priv->button); -} + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); -static void -gtk_combo_box_destroy (GtkWidget *widget) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + add_tearoffs = add_tearoffs != FALSE; - if (combo_box->priv->popup_idle_id > 0) + if (combo_box->priv->add_tearoffs != add_tearoffs) { - g_source_remove (combo_box->priv->popup_idle_id); - combo_box->priv->popup_idle_id = 0; - } - - gtk_combo_box_popdown (combo_box); - - if (combo_box->priv->row_separator_destroy) - combo_box->priv->row_separator_destroy (combo_box->priv->row_separator_data); + combo_box->priv->add_tearoffs = add_tearoffs; - combo_box->priv->row_separator_func = NULL; - combo_box->priv->row_separator_data = NULL; - combo_box->priv->row_separator_destroy = NULL; + if (GTK_IS_TREE_MENU (combo_box->priv->popup_widget)) + { + /* menu mode */ + gtk_tree_menu_set_tearoff (GTK_TREE_MENU (combo_box->priv->popup_widget), + combo_box->priv->add_tearoffs); + /* XXX Resize ?? */ + } - GTK_WIDGET_CLASS (gtk_combo_box_parent_class)->destroy (widget); - combo_box->priv->cell_view = NULL; + g_object_notify (G_OBJECT (combo_box), "add-tearoffs"); + } } -static void -gtk_combo_box_entry_contents_changed (GtkEntry *entry, - gpointer user_data) +/** + * gtk_combo_box_get_title: + * @combo_box: a #GtkComboBox + * + * Gets the current title of the menu in tearoff mode. See + * gtk_combo_box_set_add_tearoffs(). + * + * Returns: the menu's title in tearoff mode. This is an internal copy of the + * string which must not be freed. + * + * Since: 2.10 + */ +G_CONST_RETURN gchar* +gtk_combo_box_get_title (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (user_data); - - /* - * Fixes regression reported in bug #574059. The old functionality relied on - * bug #572478. As a bugfix, we now emit the "changed" signal ourselves - * when the selection was already set to -1. - */ - if (gtk_combo_box_get_active(combo_box) == -1) - g_signal_emit_by_name (combo_box, "changed"); - else - gtk_combo_box_set_active (combo_box, -1); + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); + + return combo_box->priv->tearoff_title; } static void -gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, - gpointer user_data) +gtk_combo_box_update_title (GtkComboBox *combo_box) { - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeModel *model; - GtkTreeIter iter; - - if (gtk_combo_box_get_active_iter (combo_box, &iter)) - { - GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo_box))); - - if (entry) - { - GValue value = {0,}; - - g_signal_handlers_block_by_func (entry, - gtk_combo_box_entry_contents_changed, - combo_box); - - model = gtk_combo_box_get_model (combo_box); - - gtk_tree_model_get_value (model, &iter, - priv->text_column, &value); - g_object_set_property (G_OBJECT (entry), "text", &value); - g_value_unset (&value); - - g_signal_handlers_unblock_by_func (entry, - gtk_combo_box_entry_contents_changed, - combo_box); - } - } + gtk_combo_box_check_appearance (combo_box); + + if (combo_box->priv->popup_widget && + GTK_IS_MENU (combo_box->priv->popup_widget)) + gtk_menu_set_title (GTK_MENU (combo_box->priv->popup_widget), + combo_box->priv->tearoff_title); } -static GObject * -gtk_combo_box_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_properties) +/** + * gtk_combo_box_set_title: + * @combo_box: a #GtkComboBox + * @title: a title for the menu in tearoff mode + * + * Sets the menu's title in tearoff mode. + * + * Since: 2.10 + */ +void +gtk_combo_box_set_title (GtkComboBox *combo_box, + const gchar *title) { - GObject *object; - GtkComboBox *combo_box; GtkComboBoxPrivate *priv; - object = G_OBJECT_CLASS (gtk_combo_box_parent_class)->constructor - (type, n_construct_properties, construct_properties); + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - combo_box = GTK_COMBO_BOX (object); - priv = combo_box->priv; + priv = combo_box->priv; - if (priv->has_entry) + if (strcmp (title ? title : "", + priv->tearoff_title ? priv->tearoff_title : "") != 0) { - GtkWidget *entry; - - entry = gtk_entry_new (); - gtk_widget_show (entry); - gtk_container_add (GTK_CONTAINER (combo_box), entry); - - priv->text_renderer = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), - priv->text_renderer, TRUE); + g_free (priv->tearoff_title); + priv->tearoff_title = g_strdup (title); - gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), -1); + gtk_combo_box_update_title (combo_box); - g_signal_connect (combo_box, "changed", - G_CALLBACK (gtk_combo_box_entry_active_changed), NULL); + g_object_notify (G_OBJECT (combo_box), "tearoff-title"); } - - return object; } -static void -gtk_combo_box_dispose(GObject* object) +/** + * gtk_combo_box_set_popup_fixed_width: + * @combo_box: a #GtkComboBox + * @fixed: whether to use a fixed popup width + * + * Specifies whether the popup's width should be a fixed width + * matching the allocated width of the combo box. + * + * Since: 3.0 + **/ +void +gtk_combo_box_set_popup_fixed_width (GtkComboBox *combo_box, + gboolean fixed) { - GtkComboBox *combo_box = GTK_COMBO_BOX (object); + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - if (GTK_IS_MENU (combo_box->priv->popup_widget)) + if (combo_box->priv->popup_fixed_width != fixed) { - gtk_combo_box_menu_destroy (combo_box); - gtk_menu_detach (GTK_MENU (combo_box->priv->popup_widget)); - combo_box->priv->popup_widget = NULL; - } + combo_box->priv->popup_fixed_width = fixed; - G_OBJECT_CLASS (gtk_combo_box_parent_class)->dispose (object); + g_object_notify (G_OBJECT (combo_box), "popup-fixed-width"); + } } -static void -gtk_combo_box_finalize (GObject *object) +/** + * gtk_combo_box_get_popup_fixed_width: + * @combo_box: a #GtkComboBox + * + * Gets whether the popup uses a fixed width matching + * the allocated width of the combo box. + * + * Returns: %TRUE if the popup uses a fixed width + * + * Since: 3.0 + **/ +gboolean +gtk_combo_box_get_popup_fixed_width (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (object); - GSList *i; - - if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view)) - gtk_combo_box_list_destroy (combo_box); - - if (combo_box->priv->popup_window) - gtk_widget_destroy (combo_box->priv->popup_window); + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - gtk_combo_box_unset_model (combo_box); + return combo_box->priv->popup_fixed_width; +} - for (i = combo_box->priv->cells; i; i = i->next) - { - ComboCellInfo *info = (ComboCellInfo *)i->data; - GSList *list = info->attributes; - if (info->destroy) - info->destroy (info->func_data); +/** + * gtk_combo_box_get_popup_accessible: + * @combo_box: a #GtkComboBox + * + * Gets the accessible object corresponding to the combo box's popup. + * + * This function is mostly intended for use by accessibility technologies; + * applications should have little use for it. + * + * Returns: (transfer none): the accessible object corresponding + * to the combo box's popup. + * + * Since: 2.6 + */ +AtkObject* +gtk_combo_box_get_popup_accessible (GtkComboBox *combo_box) +{ + AtkObject *atk_obj; - while (list && list->next) - { - g_free (list->data); - list = list->next->next; - } - g_slist_free (info->attributes); + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); - g_object_unref (info->cell); - g_slice_free (ComboCellInfo, info); - } - g_slist_free (combo_box->priv->cells); - - g_free (combo_box->priv->tearoff_title); - - G_OBJECT_CLASS (gtk_combo_box_parent_class)->finalize (object); -} - - -static gboolean -gtk_cell_editable_key_press (GtkWidget *widget, - GdkEventKey *event, - gpointer data) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (data); - - if (event->keyval == GDK_KEY_Escape) - { - g_object_set (combo_box, - "editing-canceled", TRUE, - NULL); - gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); - gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); - - return TRUE; - } - else if (event->keyval == GDK_KEY_Return || - event->keyval == GDK_KEY_ISO_Enter || - event->keyval == GDK_KEY_KP_Enter) + if (combo_box->priv->popup_widget) { - gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); - gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); - - return TRUE; + atk_obj = gtk_widget_get_accessible (combo_box->priv->popup_widget); + return atk_obj; } - return FALSE; + return NULL; } -static gboolean -popdown_idle (gpointer data) +/** + * gtk_combo_box_get_row_separator_func: + * @combo_box: a #GtkComboBox + * + * Returns the current row separator function. + * + * Return value: the current row separator function. + * + * Since: 2.6 + */ +GtkTreeViewRowSeparatorFunc +gtk_combo_box_get_row_separator_func (GtkComboBox *combo_box) { - GtkComboBox *combo_box; - - combo_box = GTK_COMBO_BOX (data); - - gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); - gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); - - g_object_unref (combo_box); + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); - return FALSE; + return combo_box->priv->row_separator_func; } -static void -popdown_handler (GtkWidget *widget, - gpointer data) +/** + * gtk_combo_box_set_row_separator_func: + * @combo_box: a #GtkComboBox + * @func: a #GtkTreeViewRowSeparatorFunc + * @data: (allow-none): user data to pass to @func, or %NULL + * @destroy: (allow-none): destroy notifier for @data, or %NULL + * + * Sets the row separator function, which is used to determine + * whether a row should be drawn as a separator. If the row separator + * function is %NULL, no separators are drawn. This is the default value. + * + * Since: 2.6 + */ +void +gtk_combo_box_set_row_separator_func (GtkComboBox *combo_box, + GtkTreeViewRowSeparatorFunc func, + gpointer data, + GDestroyNotify destroy) { - gdk_threads_add_idle (popdown_idle, g_object_ref (data)); -} + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); -static gboolean -popup_idle (gpointer data) -{ - GtkComboBox *combo_box; + if (combo_box->priv->row_separator_destroy) + combo_box->priv->row_separator_destroy (combo_box->priv->row_separator_data); - combo_box = GTK_COMBO_BOX (data); + combo_box->priv->row_separator_func = func; + combo_box->priv->row_separator_data = data; + combo_box->priv->row_separator_destroy = destroy; - if (GTK_IS_MENU (combo_box->priv->popup_widget) && - combo_box->priv->cell_view) - g_signal_connect_object (combo_box->priv->popup_widget, - "unmap", G_CALLBACK (popdown_handler), - combo_box, 0); - - /* we unset this if a menu item is activated */ - g_object_set (combo_box, - "editing-canceled", TRUE, - NULL); - gtk_combo_box_popup (combo_box); + /* Provoke the underlying treeview/menu to rebuild themselves with the new separator func */ + if (combo_box->priv->tree_view) + { + gtk_tree_view_set_model (GTK_TREE_VIEW (combo_box->priv->tree_view), NULL); + gtk_tree_view_set_model (GTK_TREE_VIEW (combo_box->priv->tree_view), combo_box->priv->model); + } - combo_box->priv->popup_idle_id = 0; - combo_box->priv->activate_button = 0; - combo_box->priv->activate_time = 0; + if (GTK_IS_TREE_MENU (combo_box->priv->popup_widget)) + { + gtk_tree_menu_set_model (GTK_TREE_MENU (combo_box->priv->popup_widget), NULL); + gtk_tree_menu_set_model (GTK_TREE_MENU (combo_box->priv->popup_widget), combo_box->priv->model); + } - return FALSE; + gtk_widget_queue_draw (GTK_WIDGET (combo_box)); } -static void -gtk_combo_box_start_editing (GtkCellEditable *cell_editable, - GdkEvent *event) +/** + * gtk_combo_box_set_button_sensitivity: + * @combo_box: a #GtkComboBox + * @sensitivity: specify the sensitivity of the dropdown button + * + * Sets whether the dropdown button of the combo box should be + * always sensitive (%GTK_SENSITIVITY_ON), never sensitive (%GTK_SENSITIVITY_OFF) + * or only if there is at least one item to display (%GTK_SENSITIVITY_AUTO). + * + * Since: 2.14 + **/ +void +gtk_combo_box_set_button_sensitivity (GtkComboBox *combo_box, + GtkSensitivityType sensitivity) { - GtkComboBox *combo_box = GTK_COMBO_BOX (cell_editable); - GtkWidget *child; - - combo_box->priv->is_cell_renderer = TRUE; - - if (combo_box->priv->cell_view) - { - g_signal_connect_object (combo_box->priv->button, "key-press-event", - G_CALLBACK (gtk_cell_editable_key_press), - cell_editable, 0); + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - gtk_widget_grab_focus (combo_box->priv->button); - } - else + if (combo_box->priv->button_sensitivity != sensitivity) { - child = gtk_bin_get_child (GTK_BIN (combo_box)); - - g_signal_connect_object (child, "key-press-event", - G_CALLBACK (gtk_cell_editable_key_press), - cell_editable, 0); + combo_box->priv->button_sensitivity = sensitivity; + gtk_combo_box_update_sensitivity (combo_box); - gtk_widget_grab_focus (child); - gtk_widget_set_can_focus (combo_box->priv->button, FALSE); + g_object_notify (G_OBJECT (combo_box), "button-sensitivity"); } +} - /* we do the immediate popup only for the optionmenu-like - * appearance - */ - if (combo_box->priv->is_cell_renderer && - combo_box->priv->cell_view && !combo_box->priv->tree_view) - { - if (event && event->type == GDK_BUTTON_PRESS) - { - GdkEventButton *event_button = (GdkEventButton *)event; - - combo_box->priv->activate_button = event_button->button; - combo_box->priv->activate_time = event_button->time; - } +/** + * gtk_combo_box_get_button_sensitivity: + * @combo_box: a #GtkComboBox + * + * Returns whether the combo box sets the dropdown button + * sensitive or not when there are no items in the model. + * + * Return Value: %GTK_SENSITIVITY_ON if the dropdown button + * is sensitive when the model is empty, %GTK_SENSITIVITY_OFF + * if the button is always insensitive or + * %GTK_SENSITIVITY_AUTO if it is only sensitive as long as + * the model has one item to be selected. + * + * Since: 2.14 + **/ +GtkSensitivityType +gtk_combo_box_get_button_sensitivity (GtkComboBox *combo_box) +{ + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - combo_box->priv->popup_idle_id = - gdk_threads_add_idle (popup_idle, combo_box); - } + return combo_box->priv->button_sensitivity; } /** - * gtk_combo_box_get_add_tearoffs: + * gtk_combo_box_get_has_entry: * @combo_box: a #GtkComboBox - * - * Gets the current value of the :add-tearoffs property. - * - * Return value: the current value of the :add-tearoffs property. - */ + * + * Returns whether the combo box has an entry. + * + * Return Value: whether there is an entry in @combo_box. + * + * Since: 2.24 + **/ gboolean -gtk_combo_box_get_add_tearoffs (GtkComboBox *combo_box) +gtk_combo_box_get_has_entry (GtkComboBox *combo_box) { g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - return combo_box->priv->add_tearoffs; + return combo_box->priv->has_entry; } /** - * gtk_combo_box_set_add_tearoffs: - * @combo_box: a #GtkComboBox - * @add_tearoffs: %TRUE to add tearoff menu items - * - * Sets whether the popup menu should have a tearoff - * menu item. + * gtk_combo_box_set_entry_text_column: + * @combo_box: A #GtkComboBox + * @text_column: A column in @model to get the strings from for + * the internal entry * - * Since: 2.6 + * Sets the model column which @combo_box should use to get strings from + * to be @text_column. The column @text_column in the model of @combo_box + * must be of type %G_TYPE_STRING. + * + * This is only relevant if @combo_box has been created with + * #GtkComboBox:has-entry as %TRUE. + * + * Since: 2.24 */ void -gtk_combo_box_set_add_tearoffs (GtkComboBox *combo_box, - gboolean add_tearoffs) +gtk_combo_box_set_entry_text_column (GtkComboBox *combo_box, + gint text_column) { + GtkComboBoxPrivate *priv = combo_box->priv; + GtkTreeModel *model; + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - add_tearoffs = add_tearoffs != FALSE; + model = gtk_combo_box_get_model (combo_box); - if (combo_box->priv->add_tearoffs != add_tearoffs) - { - combo_box->priv->add_tearoffs = add_tearoffs; - gtk_combo_box_check_appearance (combo_box); - gtk_combo_box_relayout (combo_box); - g_object_notify (G_OBJECT (combo_box), "add-tearoffs"); - } + g_return_if_fail (text_column >= 0); + g_return_if_fail (model == NULL || text_column < gtk_tree_model_get_n_columns (model)); + + priv->text_column = text_column; + + if (priv->text_renderer != NULL) + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), + priv->text_renderer, + "text", text_column, + NULL); } /** - * gtk_combo_box_get_title: - * @combo_box: a #GtkComboBox + * gtk_combo_box_get_entry_text_column: + * @combo_box: A #GtkComboBox. * - * Gets the current title of the menu in tearoff mode. See - * gtk_combo_box_set_add_tearoffs(). + * Returns the column which @combo_box is using to get the strings + * from to display in the internal entry. * - * Returns: the menu's title in tearoff mode. This is an internal copy of the - * string which must not be freed. + * Return value: A column in the data source model of @combo_box. * - * Since: 2.10 + * Since: 2.24 */ -G_CONST_RETURN gchar* -gtk_combo_box_get_title (GtkComboBox *combo_box) +gint +gtk_combo_box_get_entry_text_column (GtkComboBox *combo_box) { - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); - - return combo_box->priv->tearoff_title; -} + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0); -static void -gtk_combo_box_update_title (GtkComboBox *combo_box) -{ - gtk_combo_box_check_appearance (combo_box); - - if (combo_box->priv->popup_widget && - GTK_IS_MENU (combo_box->priv->popup_widget)) - gtk_menu_set_title (GTK_MENU (combo_box->priv->popup_widget), - combo_box->priv->tearoff_title); + return combo_box->priv->text_column; } /** - * gtk_combo_box_set_title: - * @combo_box: a #GtkComboBox - * @title: a title for the menu in tearoff mode - * - * Sets the menu's title in tearoff mode. + * gtk_combo_box_set_focus_on_click: + * @combo: a #GtkComboBox + * @focus_on_click: whether the combo box grabs focus when clicked + * with the mouse + * + * Sets whether the combo box will grab focus when it is clicked with + * the mouse. Making mouse clicks not grab focus is useful in places + * like toolbars where you don't want the keyboard focus removed from + * the main area of the application. * - * Since: 2.10 + * Since: 2.6 */ void -gtk_combo_box_set_title (GtkComboBox *combo_box, - const gchar *title) -{ - GtkComboBoxPrivate *priv; - - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - - priv = combo_box->priv; - - if (strcmp (title ? title : "", - priv->tearoff_title ? priv->tearoff_title : "") != 0) - { - g_free (priv->tearoff_title); - priv->tearoff_title = g_strdup (title); - - gtk_combo_box_update_title (combo_box); - - g_object_notify (G_OBJECT (combo_box), "tearoff-title"); - } -} - - -/** - * gtk_combo_box_set_popup_fixed_width: - * @combo_box: a #GtkComboBox - * @fixed: whether to use a fixed popup width - * - * Specifies whether the popup's width should be a fixed width - * matching the allocated width of the combo box. - * - * Since: 3.0 - **/ -void -gtk_combo_box_set_popup_fixed_width (GtkComboBox *combo_box, - gboolean fixed) +gtk_combo_box_set_focus_on_click (GtkComboBox *combo_box, + gboolean focus_on_click) { g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + + focus_on_click = focus_on_click != FALSE; - if (combo_box->priv->popup_fixed_width != fixed) + if (combo_box->priv->focus_on_click != focus_on_click) { - combo_box->priv->popup_fixed_width = fixed; + combo_box->priv->focus_on_click = focus_on_click; - g_object_notify (G_OBJECT (combo_box), "popup-fixed-width"); + if (combo_box->priv->button) + gtk_button_set_focus_on_click (GTK_BUTTON (combo_box->priv->button), + focus_on_click); + + g_object_notify (G_OBJECT (combo_box), "focus-on-click"); } } /** - * gtk_combo_box_get_popup_fixed_width: - * @combo_box: a #GtkComboBox - * - * Gets whether the popup uses a fixed width matching - * the allocated width of the combo box. + * gtk_combo_box_get_focus_on_click: + * @combo: a #GtkComboBox + * + * Returns whether the combo box grabs focus when it is clicked + * with the mouse. See gtk_combo_box_set_focus_on_click(). * - * Returns: %TRUE if the popup uses a fixed width + * Return value: %TRUE if the combo box grabs focus when it is + * clicked with the mouse. * - * Since: 3.0 - **/ + * Since: 2.6 + */ gboolean -gtk_combo_box_get_popup_fixed_width (GtkComboBox *combo_box) +gtk_combo_box_get_focus_on_click (GtkComboBox *combo_box) { g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - - return combo_box->priv->popup_fixed_width; + + return combo_box->priv->focus_on_click; } /** - * gtk_combo_box_get_popup_accessible: + * gtk_combo_box_popup: * @combo_box: a #GtkComboBox - * - * Gets the accessible object corresponding to the combo box's popup. + * + * Pops up the menu or dropdown list of @combo_box. * * This function is mostly intended for use by accessibility technologies; * applications should have little use for it. * - * Returns: (transfer none): the accessible object corresponding - * to the combo box's popup. - * - * Since: 2.6 + * Since: 2.4 */ -AtkObject* -gtk_combo_box_get_popup_accessible (GtkComboBox *combo_box) +void +gtk_combo_box_popup (GtkComboBox *combo_box) { - AtkObject *atk_obj; - - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); - - if (combo_box->priv->popup_widget) - { - atk_obj = gtk_widget_get_accessible (combo_box->priv->popup_widget); - return atk_obj; - } + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - return NULL; + g_signal_emit (combo_box, combo_box_signals[POPUP], 0); } /** - * gtk_combo_box_get_row_separator_func: + * gtk_combo_box_popup_for_device: * @combo_box: a #GtkComboBox - * - * Returns the current row separator function. - * - * Return value: the current row separator function. + * @device: a #GdkDevice * - * Since: 2.6 - */ -GtkTreeViewRowSeparatorFunc -gtk_combo_box_get_row_separator_func (GtkComboBox *combo_box) -{ - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL); - - return combo_box->priv->row_separator_func; -} - -/** - * gtk_combo_box_set_row_separator_func: - * @combo_box: a #GtkComboBox - * @func: a #GtkTreeViewRowSeparatorFunc - * @data: (allow-none): user data to pass to @func, or %NULL - * @destroy: (allow-none): destroy notifier for @data, or %NULL - * - * Sets the row separator function, which is used to determine - * whether a row should be drawn as a separator. If the row separator - * function is %NULL, no separators are drawn. This is the default value. + * Pops up the menu or dropdown list of @combo_box, the popup window + * will be grabbed so only @device and its associated pointer/keyboard + * are the only #GdkDevices able to send events to it. * - * Since: 2.6 - */ + * Since: 3.0 + **/ void -gtk_combo_box_set_row_separator_func (GtkComboBox *combo_box, - GtkTreeViewRowSeparatorFunc func, - gpointer data, - GDestroyNotify destroy) +gtk_combo_box_popup_for_device (GtkComboBox *combo_box, + GdkDevice *device) { - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - - if (combo_box->priv->row_separator_destroy) - combo_box->priv->row_separator_destroy (combo_box->priv->row_separator_data); + GtkComboBoxPrivate *priv = combo_box->priv; + gint x, y, width, height; + GtkTreePath *path = NULL, *ppath; + GtkWidget *toplevel; + GdkDevice *keyboard, *pointer; + guint32 time; - combo_box->priv->row_separator_func = func; - combo_box->priv->row_separator_data = data; - combo_box->priv->row_separator_destroy = destroy; + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + g_return_if_fail (GDK_IS_DEVICE (device)); - if (combo_box->priv->tree_view) - gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (combo_box->priv->tree_view), - func, data, NULL); + if (!gtk_widget_get_realized (GTK_WIDGET (combo_box))) + return; - gtk_combo_box_relayout (combo_box); + if (gtk_widget_get_mapped (priv->popup_widget)) + return; - gtk_widget_queue_draw (GTK_WIDGET (combo_box)); -} + if (priv->grab_pointer && priv->grab_keyboard) + return; -/** - * gtk_combo_box_set_button_sensitivity: - * @combo_box: a #GtkComboBox - * @sensitivity: specify the sensitivity of the dropdown button - * - * Sets whether the dropdown button of the combo box should be - * always sensitive (%GTK_SENSITIVITY_ON), never sensitive (%GTK_SENSITIVITY_OFF) - * or only if there is at least one item to display (%GTK_SENSITIVITY_AUTO). - * - * Since: 2.14 - **/ -void -gtk_combo_box_set_button_sensitivity (GtkComboBox *combo_box, - GtkSensitivityType sensitivity) -{ - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + time = gtk_get_current_event_time (); - if (combo_box->priv->button_sensitivity != sensitivity) + if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) { - combo_box->priv->button_sensitivity = sensitivity; - gtk_combo_box_update_sensitivity (combo_box); + keyboard = device; + pointer = gdk_device_get_associated_device (device); + } + else + { + pointer = device; + keyboard = gdk_device_get_associated_device (device); + } - g_object_notify (G_OBJECT (combo_box), "button-sensitivity"); + if (GTK_IS_MENU (priv->popup_widget)) + { + gtk_combo_box_menu_popup (combo_box, + priv->activate_button, + priv->activate_time); + return; } -} -/** - * gtk_combo_box_get_button_sensitivity: - * @combo_box: a #GtkComboBox - * - * Returns whether the combo box sets the dropdown button - * sensitive or not when there are no items in the model. - * - * Return Value: %GTK_SENSITIVITY_ON if the dropdown button - * is sensitive when the model is empty, %GTK_SENSITIVITY_OFF - * if the button is always insensitive or - * %GTK_SENSITIVITY_AUTO if it is only sensitive as long as - * the model has one item to be selected. - * - * Since: 2.14 - **/ -GtkSensitivityType -gtk_combo_box_get_button_sensitivity (GtkComboBox *combo_box) -{ - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - - return combo_box->priv->button_sensitivity; -} - - -/** - * gtk_combo_box_get_has_entry: - * @combo_box: a #GtkComboBox - * - * Returns whether the combo box has an entry. - * - * Return Value: whether there is an entry in @combo_box. - * - * Since: 2.24 - **/ -gboolean -gtk_combo_box_get_has_entry (GtkComboBox *combo_box) -{ - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); - - return combo_box->priv->has_entry; -} - -/** - * gtk_combo_box_set_entry_text_column: - * @combo_box: A #GtkComboBox - * @text_column: A column in @model to get the strings from for - * the internal entry - * - * Sets the model column which @combo_box should use to get strings from - * to be @text_column. The column @text_column in the model of @combo_box - * must be of type %G_TYPE_STRING. - * - * This is only relevant if @combo_box has been created with - * #GtkComboBox:has-entry as %TRUE. - * - * Since: 2.24 - */ -void -gtk_combo_box_set_entry_text_column (GtkComboBox *combo_box, - gint text_column) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeModel *model; - - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - - model = gtk_combo_box_get_model (combo_box); - - g_return_if_fail (text_column >= 0); - g_return_if_fail (model == NULL || text_column < gtk_tree_model_get_n_columns (model)); - - priv->text_column = text_column; - - if (priv->text_renderer != NULL) - gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), - priv->text_renderer, - "text", text_column, - NULL); -} - -/** - * gtk_combo_box_get_entry_text_column: - * @combo_box: A #GtkComboBox. - * - * Returns the column which @combo_box is using to get the strings - * from to display in the internal entry. - * - * Return value: A column in the data source model of @combo_box. - * - * Since: 2.24 - */ -gint -gtk_combo_box_get_entry_text_column (GtkComboBox *combo_box) -{ - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0); - - return combo_box->priv->text_column; -} + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo_box)); + if (GTK_IS_WINDOW (toplevel)) + gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (priv->popup_window)); -/** - * gtk_combo_box_set_focus_on_click: - * @combo: a #GtkComboBox - * @focus_on_click: whether the combo box grabs focus when clicked - * with the mouse - * - * Sets whether the combo box will grab focus when it is clicked with - * the mouse. Making mouse clicks not grab focus is useful in places - * like toolbars where you don't want the keyboard focus removed from - * the main area of the application. - * - * Since: 2.6 - */ -void -gtk_combo_box_set_focus_on_click (GtkComboBox *combo_box, - gboolean focus_on_click) -{ - g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + gtk_widget_show_all (priv->scrolled_window); + gtk_combo_box_list_position (combo_box, &x, &y, &width, &height); - focus_on_click = focus_on_click != FALSE; + gtk_widget_set_size_request (priv->popup_window, width, height); + gtk_window_move (GTK_WINDOW (priv->popup_window), x, y); - if (combo_box->priv->focus_on_click != focus_on_click) + if (gtk_tree_row_reference_valid (priv->active_row)) { - combo_box->priv->focus_on_click = focus_on_click; - - if (combo_box->priv->button) - gtk_button_set_focus_on_click (GTK_BUTTON (combo_box->priv->button), - focus_on_click); - - g_object_notify (G_OBJECT (combo_box), "focus-on-click"); + path = gtk_tree_row_reference_get_path (priv->active_row); + ppath = gtk_tree_path_copy (path); + if (gtk_tree_path_up (ppath)) + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->tree_view), + ppath); + gtk_tree_path_free (ppath); } -} - -/** - * gtk_combo_box_get_focus_on_click: - * @combo: a #GtkComboBox - * - * Returns whether the combo box grabs focus when it is clicked - * with the mouse. See gtk_combo_box_set_focus_on_click(). - * - * Return value: %TRUE if the combo box grabs focus when it is - * clicked with the mouse. - * - * Since: 2.6 - */ -gboolean -gtk_combo_box_get_focus_on_click (GtkComboBox *combo_box) -{ - g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); + gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view), + TRUE); - return combo_box->priv->focus_on_click; -} - - -static gboolean -gtk_combo_box_buildable_custom_tag_start (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const gchar *tagname, - GMarkupParser *parser, - gpointer *data) -{ - if (parent_buildable_iface->custom_tag_start (buildable, builder, child, - tagname, parser, data)) - return TRUE; - - return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child, - tagname, parser, data); -} - -static void -gtk_combo_box_buildable_custom_tag_end (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const gchar *tagname, - gpointer *data) -{ - if (strcmp (tagname, "attributes") == 0) - _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, - data); - else - parent_buildable_iface->custom_tag_end (buildable, builder, child, tagname, - data); -} - -static GObject * -gtk_combo_box_buildable_get_internal_child (GtkBuildable *buildable, - GtkBuilder *builder, - const gchar *childname) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (buildable); - - if (combo_box->priv->has_entry && strcmp (childname, "entry") == 0) - return G_OBJECT (gtk_bin_get_child (GTK_BIN (buildable))); - - return parent_buildable_iface->get_internal_child (buildable, builder, childname); -} - -static void -gtk_combo_box_remeasure (GtkComboBox *combo_box) -{ - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeIter iter; - GtkTreePath *path; - - if (!priv->model || - !gtk_tree_model_get_iter_first (priv->model, &iter)) - return; - - priv->minimum_width = priv->natural_width = 0; - - path = gtk_tree_path_new_from_indices (0, -1); - - do - { - gint row_min = 0, row_nat = 0; - - if (priv->cell_view) - gtk_cell_view_get_desired_width_of_row (GTK_CELL_VIEW (priv->cell_view), - path, &row_min, &row_nat); - - priv->minimum_width = MAX (priv->minimum_width, row_min); - priv->natural_width = MAX (priv->natural_width, row_nat); - - gtk_tree_path_next (path); - } - while (gtk_tree_model_iter_next (priv->model, &iter)); - - gtk_tree_path_free (path); -} - - -static void -gtk_combo_box_measure_height_for_width (GtkComboBox *combo_box, - gint avail_width, - gint *min_height, - gint *nat_height) -{ - GtkWidget *child; - GtkComboBoxPrivate *priv = combo_box->priv; - GtkTreeIter iter; - GtkTreePath *path; - gint child_min, child_nat; - - child = gtk_bin_get_child (GTK_BIN (combo_box)); - - gtk_widget_get_preferred_height_for_width (child, avail_width, - &child_min, &child_nat); - - if (!priv->model || - !gtk_tree_model_get_iter_first (priv->model, &iter)) - goto out; - - path = gtk_tree_path_new_from_indices (0, -1); + /* popup */ + gtk_widget_show (priv->popup_window); - do + if (path) { - gint row_min = 0, row_nat = 0; - - if (priv->cell_view) - gtk_cell_view_get_desired_height_for_width_of_row (GTK_CELL_VIEW (priv->cell_view), - path, avail_width, - &row_min, &row_nat); - - child_min = MAX (child_min, row_min); - child_nat = MAX (child_nat, row_nat); - - gtk_tree_path_next (path); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->tree_view), + path, NULL, FALSE); + gtk_tree_path_free (path); } - while (gtk_tree_model_iter_next (priv->model, &iter)); - - gtk_tree_path_free (path); - - out: - - if (min_height) - *min_height = child_min; - if (nat_height) - *nat_height = child_nat; -} - - -static void -gtk_combo_box_get_preferred_width (GtkWidget *widget, - gint *minimum_size, - gint *natural_size) -{ - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - GtkComboBoxPrivate *priv = combo_box->priv; - gint focus_width, focus_pad; - gint font_size, arrow_size; - PangoContext *context; - PangoFontMetrics *metrics; - PangoFontDescription *font_desc; - GtkWidget *child; - gint minimum_width, natural_width; - gint child_min, child_nat; - GtkStyleContext *style_context; - GtkStateFlags state; - GtkBorder *border; - - child = gtk_bin_get_child (GTK_BIN (widget)); - - /* common */ - gtk_widget_get_preferred_width (child, &child_min, &child_nat); - gtk_combo_box_remeasure (combo_box); - - child_min = MAX (child_min, priv->minimum_width); - child_nat = MAX (child_nat, priv->natural_width); - - gtk_widget_style_get (GTK_WIDGET (widget), - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - "arrow-size", &arrow_size, - NULL); - - style_context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); - - gtk_style_context_get (style_context, state, - "font", &font_desc, - "border-width", &border, - NULL); - - context = gtk_widget_get_pango_context (GTK_WIDGET (widget)); - metrics = pango_context_get_metrics (context, font_desc, - pango_context_get_language (context)); - font_size = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + - pango_font_metrics_get_descent (metrics)); - pango_font_metrics_unref (metrics); - pango_font_description_free (font_desc); - - arrow_size = MAX (arrow_size, font_size); - - gtk_widget_set_size_request (priv->arrow, arrow_size, arrow_size); - - if (!priv->tree_view) - { - /* menu mode */ - - if (priv->cell_view) - { - gint sep_width, arrow_width; - gint border_width, xpad; - GtkBorder button_border; - - border_width = gtk_container_get_border_width (GTK_CONTAINER (combo_box)); - get_widget_border (priv->button, &button_border); - - gtk_widget_get_preferred_width (priv->separator, &sep_width, NULL); - gtk_widget_get_preferred_width (priv->arrow, &arrow_width, NULL); - xpad = 2 * (border_width + focus_width + focus_pad) + - button_border.left + button_border.right; - - minimum_width = child_min + sep_width + arrow_width + xpad; - natural_width = child_nat + sep_width + arrow_width + xpad; - } - else - { - gint but_width, but_nat_width; + gtk_widget_grab_focus (priv->popup_window); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), + TRUE); - gtk_widget_get_preferred_width (priv->button, - &but_width, &but_nat_width); + if (!gtk_widget_has_focus (priv->tree_view)) + gtk_widget_grab_focus (priv->tree_view); - minimum_width = child_min + but_width; - natural_width = child_nat + but_nat_width; - } - } - else + if (!popup_grab_on_window (gtk_widget_get_window (priv->popup_window), + keyboard, pointer, time)) { - /* list mode */ - gint button_width, button_nat_width; - - /* sample + frame */ - minimum_width = child_min; - natural_width = child_nat; - - minimum_width += 2 * focus_width; - natural_width += 2 * focus_width; - - if (priv->cell_view_frame) - { - if (priv->has_frame) - { - gint border_width, xpad; - GtkBorder frame_border; - - border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->cell_view_frame)); - get_widget_border (priv->cell_view_frame, &frame_border); - xpad = (2 * border_width) + frame_border.left + frame_border.right; - - minimum_width += xpad; - natural_width += xpad; - } - } - - /* the button */ - gtk_widget_get_preferred_width (priv->button, - &button_width, &button_nat_width); - - minimum_width += button_width; - natural_width += button_nat_width; + gtk_widget_hide (priv->popup_window); + return; } - minimum_width += border->left + border->right; - natural_width += border->left + border->right; - gtk_border_free (border); - - if (minimum_size) - *minimum_size = minimum_width; - - if (natural_size) - *natural_size = natural_width; -} - -static void -gtk_combo_box_get_preferred_height (GtkWidget *widget, - gint *minimum_size, - gint *natural_size) -{ - gint min_width; - - /* Combo box is height-for-width only - * (so we always just reserve enough height for the minimum width) */ - GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); - GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_width, minimum_size, natural_size); -} - -static void -gtk_combo_box_get_preferred_width_for_height (GtkWidget *widget, - gint avail_size, - gint *minimum_size, - gint *natural_size) -{ - /* Combo box is height-for-width only - * (so we assume we always reserved enough height for the minimum width) */ - GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_size, natural_size); + gtk_device_grab_add (priv->popup_window, pointer, TRUE); + priv->grab_pointer = pointer; + priv->grab_keyboard = keyboard; } - -static void -gtk_combo_box_get_preferred_height_for_width (GtkWidget *widget, - gint avail_size, - gint *minimum_size, - gint *natural_size) +/** + * gtk_combo_box_popdown: + * @combo_box: a #GtkComboBox + * + * Hides the menu or dropdown list of @combo_box. + * + * This function is mostly intended for use by accessibility technologies; + * applications should have little use for it. + * + * Since: 2.4 + */ +void +gtk_combo_box_popdown (GtkComboBox *combo_box) { - GtkComboBox *combo_box = GTK_COMBO_BOX (widget); - GtkComboBoxPrivate *priv = combo_box->priv; - gint focus_width, focus_pad; - gint min_height, nat_height; - gint size; - GtkBorder border; - - gtk_widget_style_get (GTK_WIDGET (widget), - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - NULL); - - get_widget_border (widget, &border); - size = avail_size - border.left; - - if (!priv->tree_view) - { - /* menu mode */ - if (priv->cell_view) - { - /* calculate x/y padding and separator/arrow size */ - gint sep_width, arrow_width, sep_height, arrow_height; - gint border_width, xpad, ypad; - GtkBorder button_border; - - border_width = gtk_container_get_border_width (GTK_CONTAINER (combo_box)); - get_widget_border (priv->button, &button_border); - - gtk_widget_get_preferred_width (priv->separator, &sep_width, NULL); - gtk_widget_get_preferred_width (priv->arrow, &arrow_width, NULL); - gtk_widget_get_preferred_height_for_width (priv->separator, - sep_width, &sep_height, NULL); - gtk_widget_get_preferred_height_for_width (priv->arrow, - arrow_width, &arrow_height, NULL); - - xpad = 2 * (border_width + focus_width + focus_pad) + - button_border.left + button_border.right; - ypad = 2 * (border_width + focus_width + focus_pad) + - button_border.top + button_border.bottom; - - size -= sep_width + arrow_width + xpad; - - gtk_combo_box_measure_height_for_width (combo_box, size, &min_height, &nat_height); - - arrow_height = MAX (arrow_height, sep_height); - min_height = MAX (min_height, arrow_height); - nat_height = MAX (nat_height, arrow_height); - - min_height += ypad; - nat_height += ypad; - } - else - { - /* there is a custom child widget inside (no priv->cell_view) */ - gint but_width, but_height; - - gtk_widget_get_preferred_width (priv->button, &but_width, NULL); - gtk_widget_get_preferred_height_for_width (priv->button, - but_width, &but_height, NULL); + GtkComboBoxPrivate *priv = combo_box->priv; - size -= but_width; + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - gtk_combo_box_measure_height_for_width (combo_box, size, &min_height, &nat_height); - - min_height = MAX (min_height, but_height); - nat_height = MAX (nat_height, but_height); - } - } - else + if (GTK_IS_MENU (priv->popup_widget)) { - /* list mode */ - gint but_width, but_height; - gint xpad = 0, ypad = 0; - - gtk_widget_get_preferred_width (priv->button, &but_width, NULL); - gtk_widget_get_preferred_height_for_width (priv->button, - but_width, &but_height, NULL); - - if (priv->cell_view_frame && priv->has_frame) - { - GtkBorder frame_border; - gint border_width; - - border_width = gtk_container_get_border_width (GTK_CONTAINER (priv->cell_view_frame)); - get_widget_border (GTK_WIDGET (priv->cell_view_frame), &frame_border); - - xpad = (2 * border_width) + border.left + frame_border.right; - ypad = (2 * border_width) + border.top + frame_border.bottom; - } - - size -= but_width; - size -= 2 * focus_width; - size -= xpad; - - gtk_combo_box_measure_height_for_width (combo_box, size, &min_height, &nat_height); - - min_height = MAX (min_height, but_height); - nat_height = MAX (nat_height, but_height); - - min_height += ypad; - nat_height += ypad; + gtk_menu_popdown (GTK_MENU (priv->popup_widget)); + return; } - min_height += border.top + border.bottom; - nat_height += border.top + border.bottom; + if (!gtk_widget_get_realized (GTK_WIDGET (combo_box))) + return; - if (minimum_size) - *minimum_size = min_height; + gtk_device_grab_remove (priv->popup_window, priv->grab_pointer); + gtk_widget_hide (priv->popup_window); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), + FALSE); - if (natural_size) - *natural_size = nat_height; + priv->grab_pointer = NULL; + priv->grab_keyboard = NULL; } /** diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c index 72120876bc..db970c99ff 100644 --- a/gtk/gtktreemenu.c +++ b/gtk/gtktreemenu.c @@ -115,6 +115,8 @@ static void item_activated_cb (GtkMenuItem static void submenu_activated_cb (GtkTreeMenu *submenu, const gchar *path, GtkTreeMenu *menu); +static void gtk_tree_menu_set_model_internal (GtkTreeMenu *menu, + GtkTreeModel *model); @@ -828,7 +830,7 @@ row_changed_cb (GtkTreeModel *model, GtkTreePath *root_path = gtk_tree_row_reference_get_path (priv->root); - if (gtk_tree_path_compare (root_path, path) == 0) + if (root_path && gtk_tree_path_compare (root_path, path) == 0) { if (priv->header_func) has_header = @@ -853,9 +855,9 @@ row_changed_cb (GtkTreeModel *model, priv->menu_with_header = FALSE; } + + gtk_tree_path_free (root_path); } - - gtk_tree_path_free (root_path); } if (item) @@ -940,7 +942,7 @@ area_apply_attributes_cb (GtkCellArea *area, /* If there is no submenu, go ahead and update item sensitivity, * items with submenus are always sensitive */ - if (!gtk_menu_item_get_submenu (GTK_MENU_ITEM (item))) + if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item))) { sensitive = area_is_sensitive (priv->area); @@ -1100,6 +1102,7 @@ gtk_tree_menu_create_item (GtkTreeMenu *menu, if (is_separator) { item = gtk_separator_menu_item_new (); + gtk_widget_show (item); g_object_set_qdata_full (G_OBJECT (item), tree_menu_path_quark, @@ -1138,9 +1141,8 @@ gtk_tree_menu_create_item (GtkTreeMenu *menu, priv->header_data, priv->header_destroy); - gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model); + gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model); gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); g_signal_connect (submenu, "menu-activate", @@ -1274,44 +1276,15 @@ submenu_activated_cb (GtkTreeMenu *submenu, g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path); } -/**************************************************************** - * API * - ****************************************************************/ -GtkWidget * -gtk_tree_menu_new (void) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL); -} - -GtkWidget * -gtk_tree_menu_new_with_area (GtkCellArea *area) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, - "cell-area", area, - NULL); -} - -GtkWidget * -gtk_tree_menu_new_full (GtkCellArea *area, - GtkTreeModel *model, - GtkTreePath *root) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, - "cell-area", area, - "model", model, - "root", root, - NULL); -} - -void -gtk_tree_menu_set_model (GtkTreeMenu *menu, - GtkTreeModel *model) +/* Sets the model without rebuilding the menu, prevents + * infinite recursion while building submenus (we wait + * until the root is set and then build the menu) */ +static void +gtk_tree_menu_set_model_internal (GtkTreeMenu *menu, + GtkTreeModel *model) { GtkTreeMenuPrivate *priv; - g_return_if_fail (GTK_IS_TREE_MENU (menu)); - g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); - priv = menu->priv; if (priv->model != model) @@ -1354,6 +1327,47 @@ gtk_tree_menu_set_model (GtkTreeMenu *menu, } } +/**************************************************************** + * API * + ****************************************************************/ +GtkWidget * +gtk_tree_menu_new (void) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL); +} + +GtkWidget * +gtk_tree_menu_new_with_area (GtkCellArea *area) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, + "cell-area", area, + NULL); +} + +GtkWidget * +gtk_tree_menu_new_full (GtkCellArea *area, + GtkTreeModel *model, + GtkTreePath *root) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, + "cell-area", area, + "model", model, + "root", root, + NULL); +} + +void +gtk_tree_menu_set_model (GtkTreeMenu *menu, + GtkTreeModel *model) +{ + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); + + gtk_tree_menu_set_model_internal (menu, model); + + rebuild_menu (menu); +} + GtkTreeModel * gtk_tree_menu_get_model (GtkTreeMenu *menu) { -- 2.30.2